File indexing completed on 2024-05-12 04:06:26
0001 /* 0002 SPDX-FileCopyrightText: 2009-2011 Stefan Majewsky <majewsky@gmx.net> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "collection.h" 0008 #include "collection_p.h" 0009 #include "components.h" 0010 #include "puzzle.h" 0011 0012 #include <QDir> 0013 #include <QFile> 0014 #include <QFileInfo> 0015 #include <QUuid> 0016 #include <QStandardPaths> 0017 #include <QProgressDialog> 0018 #include <KConfig> 0019 #include <KConfigGroup> 0020 #include <KLocalizedString> 0021 #include <QRegularExpression> 0022 0023 //BEGIN Palapeli::Collection::Item 0024 0025 Palapeli::Collection::Item::Item(Palapeli::Puzzle* puzzle) 0026 : m_puzzle(puzzle) 0027 { 0028 //NOTE: Previously, the metadata.modifyProtection flag was used to decide 0029 //whether the puzzle is deletable. The current implementation uses the 0030 //identifier: Puzzles that have been imported by the user have UUID 0031 //identifiers. QUuid::createUuid().toString() always encloses the UUID in 0032 //curly braces. 0033 const QString id = puzzle->identifier(); 0034 setData(id, Palapeli::Collection::IdentifierRole); 0035 setData(id.startsWith(QLatin1Char('{')), Palapeli::Collection::IsDeleteableRole); 0036 setData(i18n("Loading puzzle..."), Qt::DisplayRole); 0037 setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); 0038 //request metadata 0039 puzzle->get(Palapeli::PuzzleComponent::Metadata); 0040 populate (); 0041 //take ownership of puzzle 0042 m_puzzle->QObject::setParent(this); 0043 } 0044 0045 void Palapeli::Collection::Item::populate() 0046 { 0047 const Palapeli::MetadataComponent* cmp = m_puzzle->component<Palapeli::MetadataComponent>(); 0048 if (!cmp) 0049 return; 0050 const Palapeli::PuzzleMetadata metadata = cmp->metadata; 0051 setData(metadata.name, Palapeli::Collection::NameRole); 0052 setData(metadata.comment, Palapeli::Collection::CommentRole); 0053 setData(metadata.author, Palapeli::Collection::AuthorRole); 0054 setData(metadata.pieceCount, Palapeli::Collection::PieceCountRole); 0055 setData(metadata.thumbnail, Palapeli::Collection::ThumbnailRole); 0056 } 0057 0058 //END Palapeli::Collection::Item 0059 0060 Palapeli::Collection* Palapeli::Collection::instance(QWidget *parent) 0061 { 0062 static Palapeli::Collection instance(parent); 0063 return &instance; 0064 } 0065 0066 static QString readPseudoUrl(const QString& path_, bool local) 0067 { 0068 static const QLatin1String pseudoUrl("palapeli:/"); 0069 if (path_.startsWith(pseudoUrl)) 0070 { 0071 const QString path = path_.mid(pseudoUrl.size() + 1); 0072 if (local) 0073 { 0074 // this file can exist, if not, make sure all is ready 0075 // for creating the file 0076 // --> simulate KStandardDirs::locateLocal() 0077 const QString loc = 0078 QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + 0079 path; 0080 QFileInfo fi(loc); 0081 if (fi.exists()) 0082 return loc; 0083 // file does not exist, make sure directory exists 0084 QDir d(fi.absoluteDir()); 0085 if (!d.exists()) 0086 d.mkpath(fi.absolutePath()); 0087 return loc; 0088 } 0089 else 0090 { 0091 // this file *must* exist 0092 return QStandardPaths::locate(QStandardPaths::AppDataLocation, 0093 path, 0094 QStandardPaths::LocateFile); 0095 } 0096 } 0097 else 0098 return path_; 0099 } 0100 0101 Palapeli::Collection::Collection(QWidget *parent) 0102 : m_config(new KConfig(QStringLiteral("palapeli-collectionrc"), KConfig::CascadeConfig)) 0103 , m_group(new KConfigGroup(m_config, QStringLiteral("Palapeli Collection"))) 0104 { 0105 //read the puzzles 0106 const QStringList puzzleIds = m_group->groupList(); 0107 int len = puzzleIds.length (); 0108 int count = 0; 0109 QProgressDialog dlg(i18n("Loading collection"), QString(), 0, len, parent); 0110 dlg.setMinimumDuration(2000); 0111 dlg.setWindowModality(Qt::WindowModal); 0112 for (const QString& puzzleId : puzzleIds) 0113 { 0114 dlg.setValue(count++); 0115 KConfigGroup* puzzleGroup = new KConfigGroup(m_group, puzzleId); 0116 //find involved files 0117 const QString basePath = puzzleGroup->readEntry("Location", QString()); 0118 const QString path = readPseudoUrl(basePath, false); 0119 QString baseDesktopPath(basePath); 0120 baseDesktopPath.replace(QRegularExpression(QStringLiteral("\\.puzzle$")), QLatin1String(".desktop")); 0121 const QString desktopPath = readPseudoUrl(baseDesktopPath, false); 0122 //construct puzzle with CollectionStorageComponent 0123 if (!path.isEmpty() && (desktopPath.isEmpty() || QFileInfo(path).lastModified() >= QFileInfo(desktopPath).lastModified())) 0124 { 0125 Palapeli::Puzzle* puzzle = new Palapeli::Puzzle(new Palapeli::CollectionStorageComponent(puzzleGroup), path, puzzleId); 0126 appendRow(new Item(puzzle)); 0127 continue; 0128 } 0129 //no success - try to construct with RetailStorageComponent 0130 if (desktopPath.isEmpty()) 0131 continue; 0132 const QString puzzlePath = readPseudoUrl(basePath, true); 0133 Palapeli::Puzzle* puzzle = new Palapeli::Puzzle(new Palapeli::RetailStorageComponent(desktopPath), puzzlePath, puzzleId); 0134 appendRow(new Item(puzzle)); 0135 delete puzzleGroup; 0136 //make sure puzzle gets converted to archive format 0137 puzzle->get(Palapeli::PuzzleComponent::ArchiveStorage); 0138 } 0139 /* Moved out of CollectionStorageComponent, where we'd potentially call fdatasync on every 0140 puzzle. */ 0141 m_config->sync(); 0142 } 0143 0144 Palapeli::Collection::~Collection() 0145 { 0146 delete m_config; 0147 delete m_group; 0148 } 0149 0150 Palapeli::Puzzle* Palapeli::Collection::puzzleFromIndex(const QModelIndex& index) const 0151 { 0152 //a simple lookup like dynamic_cast<Item*>(itemFromIndex(index))->puzzle() 0153 //does not work because of proxy models in the CollectionView 0154 const QString identifier = index.data(IdentifierRole).toString(); 0155 const int count = rowCount(); 0156 for (int row = 0; row < count; ++row) 0157 { 0158 Item* item = dynamic_cast<Item*>(this->item(row)); 0159 if (item && item->puzzle()->identifier() == identifier) 0160 return item->puzzle(); 0161 } 0162 return nullptr; 0163 } 0164 0165 void Palapeli::Collection::importPuzzle(Palapeli::Puzzle* puzzle) 0166 { 0167 //determine new location 0168 const QString id = puzzle->identifier(); 0169 const QString palapeliUrl = QStringLiteral("palapeli:///collection/%1.puzzle").arg(id); 0170 puzzle->setLocation(readPseudoUrl(palapeliUrl, true)); 0171 //store puzzle 0172 puzzle->get(Palapeli::PuzzleComponent::ArchiveStorage); 0173 //create the config group for this puzzle (use pseudo-URL to avoid problems 0174 //when the configuration directory is moved) 0175 KConfigGroup puzzleGroup(m_group, id); 0176 puzzleGroup.writeEntry("Location", palapeliUrl); 0177 m_config->sync(); 0178 //add to the model 0179 appendRow(new Item(puzzle)); 0180 } 0181 0182 Palapeli::Puzzle* Palapeli::Collection::importPuzzle(const QString& path) 0183 { 0184 const QString id = Palapeli::Puzzle::fsIdentifier(path); 0185 Palapeli::Puzzle* puzzle = new Palapeli::Puzzle(new Palapeli::ArchiveStorageComponent, path, id); 0186 //insert a copy of this puzzle into the collection 0187 const QString newId = QUuid::createUuid().toString(); 0188 Palapeli::Puzzle* newPuzzle = new Palapeli::Puzzle(new Palapeli::CopyComponent(puzzle), path, newId); 0189 importPuzzle(newPuzzle); 0190 //cleanup 0191 puzzle->QObject::setParent(newPuzzle); 0192 return newPuzzle; 0193 } 0194 0195 void Palapeli::Collection::exportPuzzle(const QModelIndex& index, const QString& path) 0196 { 0197 //find existing puzzle 0198 Palapeli::Puzzle* puzzle = puzzleFromIndex(index); 0199 if (!puzzle) 0200 return; 0201 //create a copy of the given puzzle, and relocate it to the new location 0202 const QString identifier = Palapeli::Puzzle::fsIdentifier(path); 0203 Palapeli::Puzzle* newPuzzle = new Palapeli::Puzzle(new Palapeli::CopyComponent(puzzle), path, identifier); 0204 newPuzzle->get(Palapeli::PuzzleComponent::ArchiveStorage); 0205 } 0206 0207 bool Palapeli::Collection::deletePuzzle(const QModelIndex& index) 0208 { 0209 Palapeli::Puzzle* puzzle = puzzleFromIndex(index); 0210 if (!puzzle) 0211 return false; 0212 //check whether that particular file can be removed 0213 if (!QFile(puzzle->location()).remove()) 0214 return false; 0215 //remove puzzle from config 0216 KConfigGroup(m_group, puzzle->identifier()).deleteGroup(); 0217 m_config->sync(); 0218 //remove puzzle from model and delete it 0219 const int count = rowCount(); 0220 for (int row = 0; row < count; ++row) 0221 { 0222 Item* item = dynamic_cast<Item*>(this->item(row)); 0223 if (item && item->puzzle() == puzzle) 0224 qDeleteAll(this->takeRow(row)); 0225 } 0226 return true; 0227 } 0228 0229 0230 // 0231 0232 #include "moc_collection.cpp" 0233 #include "moc_collection_p.cpp"