File indexing completed on 2024-04-28 15:40:17

0001 // SPDX-FileCopyrightText: 2019-2022 The KPhotoAlbum Development Team
0002 //
0003 // SPDX-License-Identifier: GPL-2.0-or-later
0004 
0005 #include "RemoteInterface.h"
0006 
0007 #include "RemoteCommand.h"
0008 #include "RemoteImageRequest.h"
0009 #include "Server.h"
0010 #include "Types.h"
0011 
0012 #include <Browser/FlatCategoryModel.h>
0013 #include <DB/Category.h>
0014 #include <DB/CategoryCollection.h>
0015 #include <DB/CategoryPtr.h>
0016 #include <DB/ImageDB.h>
0017 #include <DB/ImageInfo.h>
0018 #include <DB/ImageInfoPtr.h>
0019 #include <DB/ImageSearchInfo.h>
0020 #include <ImageManager/AsyncLoader.h>
0021 #include <MainWindow/DirtyIndicator.h>
0022 #include <Utilities/DescriptionUtil.h>
0023 
0024 #include <KLocalizedString>
0025 #include <QBuffer>
0026 #include <QDataStream>
0027 #include <QImage>
0028 #include <QPainter>
0029 #include <QTcpSocket>
0030 #include <algorithm>
0031 #include <kiconloader.h>
0032 #include <tuple>
0033 
0034 using namespace RemoteControl;
0035 
0036 RemoteInterface &RemoteInterface::instance()
0037 {
0038     static RemoteInterface instance;
0039     return instance;
0040 }
0041 
0042 RemoteInterface::RemoteInterface(QObject *parent)
0043     : QObject(parent)
0044     , m_connection(new Server(this))
0045 {
0046     connect(m_connection, &Server::gotCommand, this, &RemoteInterface::handleCommand);
0047     connect(m_connection, &Server::connected, this, &RemoteInterface::connected);
0048     connect(m_connection, &Server::disConnected, this, &RemoteInterface::disConnected);
0049     connect(m_connection, &Server::stoppedListening, this, &RemoteInterface::stoppedListening);
0050 }
0051 
0052 DB::ImageSearchInfo RemoteInterface::convert(const SearchInfo &searchInfo) const
0053 {
0054     DB::ImageSearchInfo dbSearchInfo;
0055     QString category;
0056     QString value;
0057     for (auto item : searchInfo.values()) {
0058         std::tie(category, value) = item;
0059         dbSearchInfo.addAnd(category, value);
0060     }
0061     return dbSearchInfo;
0062 }
0063 
0064 void RemoteInterface::pixmapLoaded(ImageManager::ImageRequest *request, const QImage &image)
0065 {
0066     m_connection->sendCommand(ThumbnailResult(m_imageNameStore[request->databaseFileName()], QString(),
0067                                               image, static_cast<RemoteImageRequest *>(request)->type()));
0068 }
0069 
0070 bool RemoteInterface::requestStillNeeded(const DB::FileName &fileName)
0071 {
0072     return m_activeReuqest.contains(fileName);
0073 }
0074 
0075 void RemoteInterface::listen(QHostAddress address)
0076 {
0077     m_connection->listen(address);
0078     Q_EMIT listening();
0079 }
0080 
0081 void RemoteInterface::stopListening()
0082 {
0083     m_connection->stopListening();
0084 }
0085 
0086 void RemoteInterface::connectTo(const QHostAddress &address)
0087 {
0088     m_connection->connectToTcpServer(address);
0089 }
0090 
0091 void RemoteInterface::handleCommand(const RemoteCommand &command)
0092 {
0093     if (command.commandType() == CommandType::SearchRequest) {
0094         const SearchRequest &searchCommand = static_cast<const SearchRequest &>(command);
0095         if (searchCommand.type == SearchType::Categories)
0096             sendCategoryNames(searchCommand);
0097         else if (searchCommand.type == SearchType::CategoryItems)
0098             sendCategoryValues(searchCommand);
0099         else
0100             sendImageSearchResult(searchCommand.searchInfo);
0101     } else if (command.commandType() == CommandType::ThumbnailRequest)
0102         requestThumbnail(static_cast<const ThumbnailRequest &>(command));
0103     else if (command.commandType() == CommandType::ThumbnailCancelRequest)
0104         cancelRequest(static_cast<const ThumbnailCancelRequest &>(command));
0105     else if (command.commandType() == CommandType::ImageDetailsRequest)
0106         sendImageDetails(static_cast<const ImageDetailsRequest &>(command));
0107     else if (command.commandType() == CommandType::StaticImageRequest)
0108         sendHomePageImages(static_cast<const StaticImageRequest &>(command));
0109     else if (command.commandType() == CommandType::ToggleTokenRequest)
0110         setToken(static_cast<const ToggleTokenRequest &>(command));
0111 }
0112 
0113 void RemoteInterface::sendCategoryNames(const SearchRequest &search)
0114 {
0115     const DB::ImageSearchInfo dbSearchInfo = convert(search.searchInfo);
0116 
0117     CategoryListResult command;
0118     for (const DB::CategoryPtr &category : DB::ImageDB::instance()->categoryCollection()->categories()) {
0119         if (category->type() == DB::Category::MediaTypeCategory)
0120             continue;
0121         QMap<QString, DB::CountWithRange> images = DB::ImageDB::instance()->classify(dbSearchInfo, category->name(), DB::Image);
0122 
0123         QMap<QString, DB::CountWithRange> videos = DB::ImageDB::instance()->classify(dbSearchInfo, category->name(), DB::Video);
0124         const bool enabled = (images.count() /*+ videos.count()*/ > 1);
0125         CategoryViewType type = (category->viewType() == DB::Category::IconView || category->viewType() == DB::Category::ThumbedIconView)
0126             ? Types::CategoryIconView
0127             : Types::CategoryListView;
0128 
0129         const QImage icon = category->icon(search.size, enabled ? KIconLoader::DefaultState : KIconLoader::DisabledState).toImage();
0130         command.categories.append({ category->name(), icon, enabled, type });
0131     }
0132     m_connection->sendCommand(command);
0133 }
0134 
0135 void RemoteInterface::sendCategoryValues(const SearchRequest &search)
0136 {
0137     const DB::ImageSearchInfo dbSearchInfo = convert(search.searchInfo);
0138     const QString categoryName = search.searchInfo.currentCategory();
0139 
0140     const DB::CategoryPtr category = DB::ImageDB::instance()->categoryCollection()->categoryForName(search.searchInfo.currentCategory());
0141 
0142     Browser::FlatCategoryModel model(category, dbSearchInfo);
0143 
0144     if (category->viewType() == DB::Category::IconView || category->viewType() == DB::Category::ThumbedIconView) {
0145         QList<int> result;
0146         std::transform(model.m_items.begin(), model.m_items.end(), std::back_inserter(result),
0147                        [this, categoryName](const QString itemName) {
0148                            return m_imageNameStore.idForCategory(categoryName, itemName);
0149                        });
0150         m_connection->sendCommand(SearchResult(SearchType::CategoryItems, result));
0151     } else {
0152         m_connection->sendCommand(CategoryItemsResult(model.m_items));
0153     }
0154 }
0155 
0156 void RemoteInterface::sendImageSearchResult(const SearchInfo &search)
0157 {
0158     const DB::FileNameList files = DB::ImageDB::instance()->search(convert(search), true /* Require on disk */).files();
0159     DB::FileNameList stacksRemoved;
0160     QList<int> result;
0161 
0162     std::remove_copy_if(files.begin(), files.end(), std::back_inserter(stacksRemoved),
0163                         [](const DB::FileName &file) {
0164                             // Only include unstacked images, and the top of stacked images.
0165                             // And also exclude videos
0166                             return DB::ImageDB::instance()->info(file)->stackOrder() > 1 || DB::ImageDB::instance()->info(file)->isVideo();
0167                         });
0168 
0169     std::transform(stacksRemoved.begin(), stacksRemoved.end(), std::back_inserter(result),
0170                    [this](const DB::FileName &fileName) {
0171                        return m_imageNameStore[fileName];
0172                    });
0173 
0174     m_connection->sendCommand(SearchResult(SearchType::Images, result));
0175 }
0176 
0177 void RemoteInterface::requestThumbnail(const ThumbnailRequest &command)
0178 {
0179     if (command.type == ViewType::CategoryItems) {
0180         auto tuple = m_imageNameStore.categoryForId(command.imageId);
0181         QString categoryName = tuple.first;
0182         QString itemName = tuple.second;
0183 
0184         const DB::CategoryPtr category = DB::ImageDB::instance()->categoryCollection()->categoryForName(categoryName);
0185         QImage image = category->categoryImage(categoryName, itemName, command.size.width(), command.size.height()).toImage();
0186         m_connection->sendCommand(ThumbnailResult(command.imageId, itemName, image, ViewType::CategoryItems));
0187     } else {
0188         const DB::FileName fileName = m_imageNameStore[command.imageId];
0189         const DB::ImageInfoPtr info = DB::ImageDB::instance()->info(fileName);
0190         const int angle = info->angle();
0191 
0192         m_activeReuqest.insert(fileName);
0193 
0194         QSize size = command.size;
0195         if (!size.isValid()) {
0196             // Request for full screen image.
0197             size = info->size();
0198         }
0199         RemoteImageRequest *request
0200             = new RemoteImageRequest(fileName, size, angle, command.type, this);
0201 
0202         ImageManager::AsyncLoader::instance()->load(request);
0203     }
0204 }
0205 
0206 void RemoteInterface::cancelRequest(const ThumbnailCancelRequest &command)
0207 {
0208     m_activeReuqest.remove(m_imageNameStore[command.imageId]);
0209 }
0210 
0211 void RemoteInterface::sendImageDetails(const ImageDetailsRequest &command)
0212 {
0213     const DB::FileName fileName = m_imageNameStore[command.imageId];
0214     const DB::ImageInfoPtr info = DB::ImageDB::instance()->info(fileName);
0215     ImageDetailsResult result;
0216     result.fileName = fileName.relative();
0217     result.date = info->date().toString();
0218     result.description = info->description();
0219     result.categories.clear();
0220     for (const QString &categoryName : info->availableCategories()) {
0221         DB::CategoryPtr category = DB::ImageDB::instance()->categoryCollection()->categoryForName(categoryName);
0222         CategoryItemDetailsList list;
0223         for (const QString &item : info->itemsOfCategory(categoryName)) {
0224             const QString age = Utilities::formatAge(category, item, info);
0225             list.append(CategoryItemDetails(item, age));
0226         }
0227         result.categories[categoryName] = list;
0228     }
0229 
0230     m_connection->sendCommand(result);
0231 }
0232 
0233 void RemoteInterface::sendHomePageImages(const StaticImageRequest &command)
0234 {
0235     const int size = command.size;
0236 
0237     QPixmap homeIcon = KIconLoader::global()->loadIcon(QString::fromUtf8("go-home"), KIconLoader::Desktop, size);
0238     QPixmap kphotoalbumIcon = KIconLoader::global()->loadIcon(QString::fromUtf8("kphotoalbum"), KIconLoader::Desktop, size);
0239     QPixmap discoverIcon = KIconLoader::global()->loadIcon(QString::fromUtf8("edit-find"), KIconLoader::Desktop, size);
0240 
0241     m_connection->sendCommand(StaticImageResult(homeIcon.toImage(), kphotoalbumIcon.toImage(), discoverIcon.toImage()));
0242 }
0243 
0244 void RemoteInterface::setToken(const ToggleTokenRequest &command)
0245 {
0246     const DB::FileName fileName = m_imageNameStore[command.imageId];
0247     DB::ImageInfoPtr info = DB::ImageDB::instance()->info(fileName);
0248     DB::CategoryPtr tokensCategory = DB::ImageDB::instance()->categoryCollection()->categoryForSpecial(DB::Category::TokensCategory);
0249     if (command.state == ToggleTokenRequest::On)
0250         info->addCategoryInfo(tokensCategory->name(), command.token);
0251     else
0252         info->removeCategoryInfo(tokensCategory->name(), command.token);
0253     MainWindow::DirtyIndicator::markDirty();
0254 }
0255 
0256 #include "moc_RemoteInterface.cpp"