1 // vim: set tabstop=4 shiftwidth=4 expandtab:
2 /*
3 Gwenview: an image viewer
4 Copyright 2009 Aurélien Gâteau <agateau@kde.org>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
19
20 */
21 // Self
22 #include "importer.h"
23
24 // Qt
25 #include <QDateTime>
26 #include <QTemporaryDir>
27 #include <QUrl>
28
29 // KF
30 #include <KFileItem>
31 #include <KIO/CopyJob>
32 #include <KIO/DeleteJob>
33 #include <KIO/Job>
34 #include <KIO/JobUiDelegate>
35 #include <KIO/MkpathJob>
36 #include <KJobWidgets>
37 #include <KLocalizedString>
38 #include <kio/jobclasses.h>
39
40 // stdc++
41 #include <memory>
42
43 // Local
44 #include "gwenview_importer_debug.h"
45 #include <QDir>
46 #include <filenameformater.h>
47 #include <fileutils.h>
48 #include <lib/timeutils.h>
49 #include <lib/urlutils.h>
50
51 namespace Gwenview
52 {
53 struct ImporterPrivate {
54 Importer *q;
55 QWidget *mAuthWindow;
56 std::unique_ptr<FileNameFormater> mFileNameFormater;
57 QUrl mTempImportDirUrl;
58 QTemporaryDir *mTempImportDir;
59 QUrl mDestinationDirUrl;
60
61 /* @defgroup reset Should be reset in start()
62 * @{ */
63 QList<QUrl> mUrlList;
64 QList<QUrl> mImportedUrlList;
65 QList<QUrl> mSkippedUrlList;
66 QList<QUrl> mFailedUrlList;
67 QList<QUrl> mFailedSubFolderList;
68 int mRenamedCount;
69 int mProgress;
70 int mJobProgress;
71 /* @} */
72
73 QUrl mCurrentUrl;
74
createImportDirGwenview::ImporterPrivate75 bool createImportDir(const QUrl &url)
76 {
77 KIO::Job *job = KIO::mkpath(url, QUrl(), KIO::HideProgressInfo);
78 KJobWidgets::setWindow(job, mAuthWindow);
79 if (!job->exec()) {
80 Q_EMIT q->error(i18n("Could not create destination folder."));
81 return false;
82 }
83
84 // Check if local and fast url. The check for fast url is needed because
85 // otherwise the retrieved date will not be correct: see implementation
86 // of TimeUtils::dateTimeForFileItem
87 if (UrlUtils::urlIsFastLocalFile(url)) {
88 QString tempDirPath = url.toLocalFile() + "/.gwenview_importer-XXXXXX";
89 mTempImportDir = new QTemporaryDir(tempDirPath);
90 } else {
91 mTempImportDir = new QTemporaryDir();
92 }
93
94 if (!mTempImportDir->isValid()) {
95 Q_EMIT q->error(i18n("Could not create temporary upload folder."));
96 return false;
97 }
98
99 mTempImportDirUrl = QUrl::fromLocalFile(mTempImportDir->path() + '/');
100 if (!mTempImportDirUrl.isValid()) {
101 Q_EMIT q->error(i18n("Could not create temporary upload folder."));
102 return false;
103 }
104
105 return true;
106 }
107
importNextGwenview::ImporterPrivate108 void importNext()
109 {
110 if (mUrlList.empty()) {
111 q->finalizeImport();
112 return;
113 }
114 mCurrentUrl = mUrlList.takeFirst();
115 QUrl dst = mTempImportDirUrl;
116 dst.setPath(dst.path() + mCurrentUrl.fileName());
117 KIO::Job *job = KIO::copy(mCurrentUrl, dst, KIO::HideProgressInfo | KIO::Overwrite);
118 KJobWidgets::setWindow(job, mAuthWindow);
119 QObject::connect(job, &KJob::result, q, &Importer::slotCopyDone);
120 QObject::connect(job, SIGNAL(percent(KJob *, ulong)), q, SLOT(slotPercent(KJob *, ulong)));
121 }
122
renameImportedUrlGwenview::ImporterPrivate123 void renameImportedUrl(const QUrl &src)
124 {
125 QUrl dst = mDestinationDirUrl;
126 QString fileName;
127 if (mFileNameFormater.get()) {
128 KFileItem item(src);
129 item.setDelayedMimeTypes(true);
130 // Get the document time, but do not cache the result because the
131 // 'src' url is temporary: if we import "foo/image.jpg" and
132 // "bar/image.jpg", both images will be temporarily saved in the
133 // 'src' url.
134 QDateTime dateTime = TimeUtils::dateTimeForFileItem(item, TimeUtils::SkipCache);
135 fileName = mFileNameFormater->format(src, dateTime);
136 } else {
137 fileName = src.fileName();
138 }
139 dst.setPath(dst.path() + '/' + fileName);
140
141 FileUtils::RenameResult result;
142 // Create additional subfolders if needed (e.g. when extra slashes in FileNameFormater)
143 QUrl subFolder = dst.adjusted(QUrl::RemoveFilename);
144 KIO::Job *job = KIO::mkpath(subFolder, QUrl(), KIO::HideProgressInfo);
145 KJobWidgets::setWindow(job, mAuthWindow);
146 if (!job->exec()) { // if subfolder creation fails
147 qCWarning(GWENVIEW_IMPORTER_LOG) << "Could not create subfolder:" << subFolder;
148 if (!mFailedSubFolderList.contains(subFolder)) {
149 mFailedSubFolderList << subFolder;
150 }
151 result = FileUtils::RenameFailed;
152 } else { // if subfolder creation succeeds
153 result = FileUtils::rename(src, dst, mAuthWindow);
154 }
155
156 switch (result) {
157 case FileUtils::RenamedOK:
158 mImportedUrlList << mCurrentUrl;
159 break;
160 case FileUtils::RenamedUnderNewName:
161 mRenamedCount++;
162 mImportedUrlList << mCurrentUrl;
163 break;
164 case FileUtils::Skipped:
165 mSkippedUrlList << mCurrentUrl;
166 break;
167 case FileUtils::RenameFailed:
168 mFailedUrlList << mCurrentUrl;
169 qCWarning(GWENVIEW_IMPORTER_LOG) << "Rename failed for" << mCurrentUrl;
170 }
171 q->advance();
172 importNext();
173 }
174 };
175
Importer(QWidget * parent)176 Importer::Importer(QWidget *parent)
177 : QObject(parent)
178 , d(new ImporterPrivate)
179 {
180 d->q = this;
181 d->mAuthWindow = parent;
182 }
183
~Importer()184 Importer::~Importer()
185 {
186 delete d;
187 }
188
setAutoRenameFormat(const QString & format)189 void Importer::setAutoRenameFormat(const QString &format)
190 {
191 if (format.isEmpty()) {
192 d->mFileNameFormater.reset(nullptr);
193 } else {
194 d->mFileNameFormater = std::make_unique<FileNameFormater>(format);
195 }
196 }
197
start(const QList<QUrl> & list,const QUrl & destination)198 void Importer::start(const QList<QUrl> &list, const QUrl &destination)
199 {
200 d->mDestinationDirUrl = destination;
201 d->mUrlList = list;
202 d->mImportedUrlList.clear();
203 d->mSkippedUrlList.clear();
204 d->mFailedUrlList.clear();
205 d->mFailedSubFolderList.clear();
206 d->mRenamedCount = 0;
207 d->mProgress = 0;
208 d->mJobProgress = 0;
209
210 emitProgressChanged();
211 Q_EMIT maximumChanged(d->mUrlList.count() * 100);
212
213 if (!d->createImportDir(destination)) {
214 qCWarning(GWENVIEW_IMPORTER_LOG) << "Could not create import dir";
215 return;
216 }
217 d->importNext();
218 }
219
slotCopyDone(KJob * _job)220 void Importer::slotCopyDone(KJob *_job)
221 {
222 auto *job = static_cast<KIO::CopyJob *>(_job);
223 QUrl url = job->destUrl();
224 if (job->error()) {
225 // Add document to failed url list and proceed with next one
226 d->mFailedUrlList << d->mCurrentUrl;
227 advance();
228 d->importNext();
229 return;
230 }
231
232 d->renameImportedUrl(url);
233 }
234
finalizeImport()235 void Importer::finalizeImport()
236 {
237 delete d->mTempImportDir;
238 Q_EMIT importFinished();
239 }
240
advance()241 void Importer::advance()
242 {
243 ++d->mProgress;
244 d->mJobProgress = 0;
245 emitProgressChanged();
246 }
247
slotPercent(KJob *,unsigned long percent)248 void Importer::slotPercent(KJob *, unsigned long percent)
249 {
250 d->mJobProgress = percent;
251 emitProgressChanged();
252 }
253
emitProgressChanged()254 void Importer::emitProgressChanged()
255 {
256 Q_EMIT progressChanged(d->mProgress * 100 + d->mJobProgress);
257 }
258
importedUrlList() const259 QList<QUrl> Importer::importedUrlList() const
260 {
261 return d->mImportedUrlList;
262 }
263
skippedUrlList() const264 QList<QUrl> Importer::skippedUrlList() const
265 {
266 return d->mSkippedUrlList;
267 }
268
failedUrlList() const269 QList<QUrl> Importer::failedUrlList() const
270 {
271 return d->mFailedUrlList;
272 }
273
failedSubFolderList() const274 QList<QUrl> Importer::failedSubFolderList() const
275 {
276 return d->mFailedSubFolderList;
277 }
278
renamedCount() const279 int Importer::renamedCount() const
280 {
281 return d->mRenamedCount;
282 }
283
284 } // namespace
285