collection.cpp 8.31 KB
Newer Older
1
/***************************************************************************
2
 *   Copyright 2009-2011 Stefan Majewsky <majewsky@gmx.net>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *
 *   This program 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 2 of the License, or (at your option) any later version.
 *
 *   This program 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.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
***************************************************************************/

#include "collection.h"
20
21
22
#include "collection_p.h"
#include "components.h"
#include "puzzle.h"
23

Yuri Chornoivan's avatar
Yuri Chornoivan committed
24
25
26
27
28
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QUuid>
#include <QStandardPaths>
29
#include <QProgressDialog>
Laurent Montel's avatar
Port++    
Laurent Montel committed
30
31
#include <KConfig>
#include <KConfigGroup>
Laurent Montel's avatar
Laurent Montel committed
32
#include <KLocalizedString>
33

34
35
36
37
//BEGIN Palapeli::Collection::Item

Palapeli::Collection::Item::Item(Palapeli::Puzzle* puzzle)
	: m_puzzle(puzzle)
38
{
39
40
41
42
43
44
45
46
	//NOTE: Previously, the metadata.modifyProtection flag was used to decide
	//whether the puzzle is deletable. The current implementation uses the
	//identifier: Puzzles that have been imported by the user have UUID
	//identifiers. QUuid::createUuid().toString() always encloses the UUID in
	//curly braces.
	const QString id = puzzle->identifier();
	setData(id, Palapeli::Collection::IdentifierRole);
	setData(id.startsWith(QChar('{')), Palapeli::Collection::IsDeleteableRole);
47
	setData(i18n("Loading puzzle..."), Qt::DisplayRole);
48
	setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
49
	//request metadata
50
51
	puzzle->get(Palapeli::PuzzleComponent::Metadata);
	populate ();
52
53
	//take ownership of puzzle
	m_puzzle->QObject::setParent(this);
54
55
}

56
void Palapeli::Collection::Item::populate()
57
{
58
59
60
61
62
63
	const Palapeli::MetadataComponent* cmp = m_puzzle->component<Palapeli::MetadataComponent>();
	if (!cmp)
		return;
	const Palapeli::PuzzleMetadata metadata = cmp->metadata;
	setData(metadata.name, Palapeli::Collection::NameRole);
	setData(metadata.comment, Palapeli::Collection::CommentRole);
64
65
66
	setData(metadata.author, Palapeli::Collection::AuthorRole);
	setData(metadata.pieceCount, Palapeli::Collection::PieceCountRole);
	setData(metadata.thumbnail, Palapeli::Collection::ThumbnailRole);
67
68
}

69
70
//END Palapeli::Collection::Item

71
Palapeli::Collection* Palapeli::Collection::instance(QWidget *parent)
72
{
73
	static Palapeli::Collection instance(parent);
74
	return &instance;
75
76
}

77
static QString readPseudoUrl(const QString& path_, bool local)
78
{
79
80
	static const QLatin1String pseudoUrl("palapeli:/");
	if (path_.startsWith(pseudoUrl))
81
	{
82
		const QString path = path_.mid(pseudoUrl.size() + 1);
83
		if (local)
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
		{
			// this file can exist, if not, make sure all is ready
			// for creating the file
			// --> simulate KStandardDirs::locateLocal()
			const QString loc =
					QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) +
					path;
			QFileInfo fi(loc);
			if (fi.exists())
				return loc;
			// file does not exist, make sure directory exists
			QDir d(fi.absoluteDir());
			if (!d.exists())
				d.mkpath(fi.absolutePath());
			return loc;
		}
100
		else
101
102
103
104
105
106
		{
			// this file *must* exist
			return QStandardPaths::locate(QStandardPaths::AppDataLocation,
										  path,
										  QStandardPaths::LocateFile);
		}
107
108
	}
	else
Stefan Majewsky's avatar
Stefan Majewsky committed
109
		return path_;
110
111
}

112
Palapeli::Collection::Collection(QWidget *parent)
Laurent Montel's avatar
Laurent Montel committed
113
	: m_config(new KConfig(QStringLiteral("palapeli-collectionrc"), KConfig::CascadeConfig))
114
115
116
117
	, m_group(new KConfigGroup(m_config, "Palapeli Collection"))
{
	//read the puzzles
	const QStringList puzzleIds = m_group->groupList();
118
119
120
121
122
	int len = puzzleIds.length ();
	int count = 0;
	QProgressDialog dlg(i18n("Loading collection"), QString(), 0, len, parent);
	dlg.setMinimumDuration(2000);
	dlg.setWindowModality(Qt::WindowModal);
123
124
	foreach (const QString& puzzleId, puzzleIds)
	{
125
		dlg.setValue(count++);
126
		KConfigGroup* puzzleGroup = new KConfigGroup(m_group, puzzleId);
127
128
		//find involved files
		const QString basePath = puzzleGroup->readEntry("Location", QString());
129
		const QString path = readPseudoUrl(basePath, false);
130
		QString baseDesktopPath(basePath);
Laurent Montel's avatar
Laurent Montel committed
131
		baseDesktopPath.replace(QRegExp("\\.puzzle$"), QLatin1String(".desktop"));
132
		const QString desktopPath = readPseudoUrl(baseDesktopPath, false);
133
134
135
		//construct puzzle with CollectionStorageComponent
		if (!path.isEmpty() && (desktopPath.isEmpty() || QFileInfo(path).lastModified() >= QFileInfo(desktopPath).lastModified()))
		{
136
			Palapeli::Puzzle* puzzle = new Palapeli::Puzzle(new Palapeli::CollectionStorageComponent(puzzleGroup), path, puzzleId);
137
			appendRow(new Item(puzzle));
138
			continue;
139
140
141
142
143
144
		}
		//no success - try to construct with RetailStorageComponent
		if (desktopPath.isEmpty())
			continue;
		const QString puzzlePath = readPseudoUrl(basePath, true);
		Palapeli::Puzzle* puzzle = new Palapeli::Puzzle(new Palapeli::RetailStorageComponent(desktopPath), puzzlePath, puzzleId);
145
		appendRow(new Item(puzzle));
146
147
148
		delete puzzleGroup;
		//make sure puzzle gets converted to archive format
		puzzle->get(Palapeli::PuzzleComponent::ArchiveStorage);
149
	}
150
151
152
	/* Moved out of CollectionStorageComponent, where we'd potentially call fdatasync on every
	   puzzle.  */
	m_config->sync();
153
154
}

155
Palapeli::Collection::~Collection()
156
{
157
158
	delete m_config;
	delete m_group;
159
160
}

161
Palapeli::Puzzle* Palapeli::Collection::puzzleFromIndex(const QModelIndex& index) const
162
{
163
164
165
166
167
168
169
170
171
172
173
	//a simple lookup like dynamic_cast<Item*>(itemFromIndex(index))->puzzle()
	//does not work because of proxy models in the CollectionView
	const QString identifier = index.data(IdentifierRole).toString();
	const int count = rowCount();
	for (int row = 0; row < count; ++row)
	{
		Item* item = dynamic_cast<Item*>(this->item(row));
		if (item && item->puzzle()->identifier() == identifier)
			return item->puzzle();
	}
	return 0;
174
175
}

176
void Palapeli::Collection::importPuzzle(Palapeli::Puzzle* puzzle)
177
{
178
179
	//determine new location
	const QString id = puzzle->identifier();
180
181
	const QString palapeliUrl = QStringLiteral("palapeli:///collection/%1.puzzle").arg(id);
	puzzle->setLocation(readPseudoUrl(palapeliUrl, true));
182
	//store puzzle
183
	puzzle->get(Palapeli::PuzzleComponent::ArchiveStorage);
184
185
186
	//create the config group for this puzzle (use pseudo-URL to avoid problems
	//when the configuration directory is moved)
	KConfigGroup puzzleGroup(m_group, id);
187
	puzzleGroup.writeEntry("Location", palapeliUrl);
188
189
190
	m_config->sync();
	//add to the model
	appendRow(new Item(puzzle));
191
192
}

Stefan Majewsky's avatar
Stefan Majewsky committed
193
Palapeli::Puzzle* Palapeli::Collection::importPuzzle(const QString& path)
194
{
Stefan Majewsky's avatar
Stefan Majewsky committed
195
196
	const QString id = Palapeli::Puzzle::fsIdentifier(path);
	Palapeli::Puzzle* puzzle = new Palapeli::Puzzle(new Palapeli::ArchiveStorageComponent, path, id);
197
198
	//insert a copy of this puzzle into the collection
	const QString newId = QUuid::createUuid().toString();
Stefan Majewsky's avatar
Stefan Majewsky committed
199
	Palapeli::Puzzle* newPuzzle = new Palapeli::Puzzle(new Palapeli::CopyComponent(puzzle), path, newId);
200
201
202
203
	importPuzzle(newPuzzle);
	//cleanup
	puzzle->QObject::setParent(newPuzzle);
	return newPuzzle;
204
205
}

Stefan Majewsky's avatar
Stefan Majewsky committed
206
void Palapeli::Collection::exportPuzzle(const QModelIndex& index, const QString& path)
207
{
208
209
210
211
212
	//find existing puzzle
	Palapeli::Puzzle* puzzle = puzzleFromIndex(index);
	if (!puzzle)
		return;
	//create a copy of the given puzzle, and relocate it to the new location
Stefan Majewsky's avatar
Stefan Majewsky committed
213
214
	const QString identifier = Palapeli::Puzzle::fsIdentifier(path);
	Palapeli::Puzzle* newPuzzle = new Palapeli::Puzzle(new Palapeli::CopyComponent(puzzle), path, identifier);
215
	newPuzzle->get(Palapeli::PuzzleComponent::ArchiveStorage);
216
217
}

218
bool Palapeli::Collection::deletePuzzle(const QModelIndex& index)
219
{
220
221
222
223
	Palapeli::Puzzle* puzzle = puzzleFromIndex(index);
	if (!puzzle)
		return false;
	//check whether that particular file can be removed
Stefan Majewsky's avatar
Stefan Majewsky committed
224
	if (!QFile(puzzle->location()).remove())
225
226
227
228
229
230
231
		return false;
	//remove puzzle from config
	KConfigGroup(m_group, puzzle->identifier()).deleteGroup();
	m_config->sync();
	//remove puzzle from model and delete it
	const int count = rowCount();
	for (int row = 0; row < count; ++row)
232
	{
233
234
235
		Item* item = dynamic_cast<Item*>(this->item(row));
		if (item && item->puzzle() == puzzle)
			qDeleteAll(this->takeRow(row));
236
	}
237
	return true;
238
239
}

Laurent Montel's avatar
Laurent Montel committed
240
241

//