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