mirror of
				https://github.com/kotatogram/kotatogram-desktop
				synced 2025-10-29 15:36:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			251 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop version of Telegram messaging app, see https://telegram.org
 | |
| 
 | |
| Telegram Desktop is free software: you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation, either version 3 of the License, or
 | |
| (at your option) any later version.
 | |
| 
 | |
| It is distributed in the hope that it will be useful,
 | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | |
| GNU General Public License for more details.
 | |
| 
 | |
| In addition, as a special exception, the copyright holders give permission
 | |
| to link the code of portions of this program with the OpenSSL library.
 | |
| 
 | |
| Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 | |
| Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 | |
| */
 | |
| #include "data/data_user_photos.h"
 | |
| 
 | |
| #include "auth_session.h"
 | |
| #include "apiwrap.h"
 | |
| #include "storage/storage_facade.h"
 | |
| #include "storage/storage_user_photos.h"
 | |
| 
 | |
| class UserPhotosSliceBuilder {
 | |
| public:
 | |
| 	using Key = UserPhotosSlice::Key;
 | |
| 
 | |
| 	UserPhotosSliceBuilder(Key key, int limitBefore, int limitAfter);
 | |
| 
 | |
| 	bool applyUpdate(const Storage::UserPhotosResult &update);
 | |
| 	bool applyUpdate(const Storage::UserPhotosSliceUpdate &update);
 | |
| 	void checkInsufficientPhotos();
 | |
| 	auto insufficientPhotosAround() const {
 | |
| 		return _insufficientPhotosAround.events();
 | |
| 	}
 | |
| 
 | |
| 	UserPhotosSlice snapshot() const;
 | |
| 
 | |
| private:
 | |
| 	void mergeSliceData(
 | |
| 		base::optional<int> count,
 | |
| 		const std::deque<PhotoId> &photoIds,
 | |
| 		base::optional<int> skippedBefore,
 | |
| 		int skippedAfter);
 | |
| 	void sliceToLimits();
 | |
| 
 | |
| 	Key _key;
 | |
| 	std::deque<PhotoId> _ids;
 | |
| 	base::optional<int> _fullCount;
 | |
| 	base::optional<int> _skippedBefore;
 | |
| 	int _skippedAfter = 0;
 | |
| 	int _limitBefore = 0;
 | |
| 	int _limitAfter = 0;
 | |
| 
 | |
| 	rpl::event_stream<PhotoId> _insufficientPhotosAround;
 | |
| 
 | |
| };
 | |
| 
 | |
| UserPhotosSlice::UserPhotosSlice(Key key) : UserPhotosSlice(
 | |
| 	key,
 | |
| 	{},
 | |
| 	base::none,
 | |
| 	base::none,
 | |
| 	0) {
 | |
| }
 | |
| 
 | |
| UserPhotosSlice::UserPhotosSlice(
 | |
| 	Key key,
 | |
| 	const std::deque<PhotoId> &ids,
 | |
| 	base::optional<int> fullCount,
 | |
| 	base::optional<int> skippedBefore,
 | |
| 	int skippedAfter)
 | |
| 	: _key(key)
 | |
| 	, _ids(ids)
 | |
| 	, _fullCount(fullCount)
 | |
| 	, _skippedBefore(skippedBefore)
 | |
| 	, _skippedAfter(skippedAfter) {
 | |
| }
 | |
| 
 | |
| base::optional<int> UserPhotosSlice::indexOf(PhotoId photoId) const {
 | |
| 	auto it = base::find(_ids, photoId);
 | |
| 	if (it != _ids.end()) {
 | |
| 		return (it - _ids.begin());
 | |
| 	}
 | |
| 	return base::none;
 | |
| }
 | |
| 
 | |
| PhotoId UserPhotosSlice::operator[](int index) const {
 | |
| 	Expects(index >= 0 && index < size());
 | |
| 
 | |
| 	return *(_ids.begin() + index);
 | |
| }
 | |
| 
 | |
| base::optional<int> UserPhotosSlice::distance(const Key &a, const Key &b) const {
 | |
| 	if (a.userId != _key.userId
 | |
| 		|| b.userId != _key.userId) {
 | |
| 		return base::none;
 | |
| 	}
 | |
| 	if (auto i = indexOf(a.photoId)) {
 | |
| 		if (auto j = indexOf(b.photoId)) {
 | |
| 			return *j - *i;
 | |
| 		}
 | |
| 	}
 | |
| 	return base::none;
 | |
| }
 | |
| 
 | |
| QString UserPhotosSlice::debug() const {
 | |
| 	auto before = _skippedBefore
 | |
| 		? (*_skippedBefore
 | |
| 			? ('(' + QString::number(*_skippedBefore) + ").. ")
 | |
| 			: QString())
 | |
| 		: QString(".. ");
 | |
| 	auto after = _skippedAfter
 | |
| 		? (" ..(" + QString::number(_skippedAfter) + ')')
 | |
| 		: QString(" ..");
 | |
| 	auto middle = (size() > 2)
 | |
| 		? QString::number((*this)[0]) + " .. " + QString::number((*this)[size() - 1])
 | |
| 		: (size() > 1)
 | |
| 		? QString::number((*this)[0]) + ' ' + QString::number((*this)[1])
 | |
| 		: ((size() > 0) ? QString::number((*this)[0]) : QString());
 | |
| 	return before + middle + after;
 | |
| }
 | |
| 
 | |
| UserPhotosSliceBuilder::UserPhotosSliceBuilder(
 | |
| 	Key key,
 | |
| 	int limitBefore,
 | |
| 	int limitAfter)
 | |
| 	: _key(key)
 | |
| 	, _limitBefore(limitBefore)
 | |
| 	, _limitAfter(limitAfter) {
 | |
| }
 | |
| 
 | |
| bool UserPhotosSliceBuilder::applyUpdate(const Storage::UserPhotosResult &update) {
 | |
| 	mergeSliceData(
 | |
| 		update.count,
 | |
| 		update.photoIds,
 | |
| 		update.skippedBefore,
 | |
| 		update.skippedAfter);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool UserPhotosSliceBuilder::applyUpdate(const Storage::UserPhotosSliceUpdate &update) {
 | |
| 	if (update.userId != _key.userId) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	auto idsCount = update.photoIds ? int(update.photoIds->size()) : 0;
 | |
| 	mergeSliceData(
 | |
| 		update.count,
 | |
| 		update.photoIds ? *update.photoIds : std::deque<PhotoId> {},
 | |
| 		update.count | func::add(-idsCount),
 | |
| 		0);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| void UserPhotosSliceBuilder::checkInsufficientPhotos() {
 | |
| 	sliceToLimits();
 | |
| }
 | |
| 
 | |
| void UserPhotosSliceBuilder::mergeSliceData(
 | |
| 		base::optional<int> count,
 | |
| 		const std::deque<PhotoId> &photoIds,
 | |
| 		base::optional<int> skippedBefore,
 | |
| 		int skippedAfter) {
 | |
| 	if (photoIds.empty()) {
 | |
| 		if (_fullCount != count) {
 | |
| 			_fullCount = count;
 | |
| 			if (_fullCount && *_fullCount <= _ids.size()) {
 | |
| 				_fullCount = _ids.size();
 | |
| 				_skippedBefore = _skippedAfter = 0;
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (count) {
 | |
| 			_fullCount = count;
 | |
| 		}
 | |
| 		_skippedAfter = skippedAfter;
 | |
| 		_ids = photoIds;
 | |
| 
 | |
| 		if (_fullCount) {
 | |
| 			_skippedBefore = *_fullCount
 | |
| 				- _skippedAfter
 | |
| 				- int(_ids.size());
 | |
| 		}
 | |
| 	}
 | |
| 	sliceToLimits();
 | |
| }
 | |
| 
 | |
| void UserPhotosSliceBuilder::sliceToLimits() {
 | |
| 	auto aroundIt = base::find(_ids, _key.photoId);
 | |
| 	auto removeFromBegin = (aroundIt - _ids.begin() - _limitBefore);
 | |
| 	auto removeFromEnd = (_ids.end() - aroundIt - _limitAfter - 1);
 | |
| 	if (removeFromEnd > 0) {
 | |
| 		_ids.erase(_ids.end() - removeFromEnd, _ids.end());
 | |
| 		_skippedAfter += removeFromEnd;
 | |
| 	}
 | |
| 	if (removeFromBegin > 0) {
 | |
| 		_ids.erase(_ids.begin(), _ids.begin() + removeFromBegin);
 | |
| 		if (_skippedBefore) {
 | |
| 			*_skippedBefore += removeFromBegin;
 | |
| 		}
 | |
| 	} else if (removeFromBegin < 0 && (!_skippedBefore || *_skippedBefore > 0)) {
 | |
| 		_insufficientPhotosAround.fire(_ids.empty() ? 0 : _ids.front());
 | |
| 	}
 | |
| }
 | |
| 
 | |
| UserPhotosSlice UserPhotosSliceBuilder::snapshot() const {
 | |
| 	return UserPhotosSlice(_key, _ids, _fullCount, _skippedBefore, _skippedAfter);
 | |
| }
 | |
| 
 | |
| rpl::producer<UserPhotosSlice> UserPhotosViewer(
 | |
| 		UserPhotosSlice::Key key,
 | |
| 		int limitBefore,
 | |
| 		int limitAfter) {
 | |
| 	return [key, limitBefore, limitAfter](auto consumer) {
 | |
| 		auto lifetime = rpl::lifetime();
 | |
| 		auto builder = lifetime.make_state<UserPhotosSliceBuilder>(
 | |
| 			key,
 | |
| 			limitBefore,
 | |
| 			limitAfter);
 | |
| 		auto applyUpdate = [=](auto &&update) {
 | |
| 			if (builder->applyUpdate(std::forward<decltype(update)>(update))) {
 | |
| 				consumer.put_next(builder->snapshot());
 | |
| 			}
 | |
| 		};
 | |
| 		auto requestPhotosAround = [user = App::user(key.userId)](
 | |
| 				PhotoId photoId) {
 | |
| 			Auth().api().requestUserPhotos(user, photoId);
 | |
| 		};
 | |
| 		builder->insufficientPhotosAround()
 | |
| 			| rpl::start_with_next(requestPhotosAround, lifetime);
 | |
| 
 | |
| 		Auth().storage().userPhotosSliceUpdated()
 | |
| 			| rpl::start_with_next(applyUpdate, lifetime);
 | |
| 
 | |
| 		Auth().storage().query(Storage::UserPhotosQuery(
 | |
| 			key,
 | |
| 			limitBefore,
 | |
| 			limitAfter))
 | |
| 			| rpl::start_with_next_done(
 | |
| 				applyUpdate,
 | |
| 				[=] { builder->checkInsufficientPhotos(); },
 | |
| 				lifetime);
 | |
| 
 | |
| 		return lifetime;
 | |
| 	};
 | |
| }
 |