File indexing completed on 2024-04-21 04:18:48

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2009 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "importer.h"
0023 
0024 // Qt
0025 #include <QDateTime>
0026 #include <QTemporaryDir>
0027 #include <QUrl>
0028 
0029 // KF
0030 #include <KFileItem>
0031 #include <KIO/CopyJob>
0032 #include <KIO/DeleteJob>
0033 #include <KIO/JobUiDelegate>
0034 #include <KIO/MkpathJob>
0035 #include <KIO/SimpleJob>
0036 #include <KJobWidgets>
0037 #include <KLocalizedString>
0038 
0039 // stdc++
0040 #include <memory>
0041 
0042 // Local
0043 #include "gwenview_importer_debug.h"
0044 #include <QDir>
0045 #include <filenameformater.h>
0046 #include <fileutils.h>
0047 #include <lib/timeutils.h>
0048 #include <lib/urlutils.h>
0049 
0050 namespace Gwenview
0051 {
0052 struct ImporterPrivate {
0053     Importer *q = nullptr;
0054     QWidget *mAuthWindow = nullptr;
0055     std::unique_ptr<FileNameFormater> mFileNameFormater;
0056     QUrl mTempImportDirUrl;
0057     QTemporaryDir *mTempImportDir = nullptr;
0058     QUrl mDestinationDirUrl;
0059 
0060     /* @defgroup reset Should be reset in start()
0061      * @{ */
0062     QList<QUrl> mUrlList;
0063     QList<QUrl> mImportedUrlList;
0064     QList<QUrl> mSkippedUrlList;
0065     QList<QUrl> mFailedUrlList;
0066     QList<QUrl> mFailedSubFolderList;
0067     int mRenamedCount;
0068     int mProgress;
0069     int mJobProgress;
0070     /* @} */
0071 
0072     QUrl mCurrentUrl;
0073 
0074     bool createImportDir(const QUrl &url)
0075     {
0076         KIO::Job *job = KIO::mkpath(url, QUrl(), KIO::HideProgressInfo);
0077         KJobWidgets::setWindow(job, mAuthWindow);
0078         if (!job->exec()) {
0079             Q_EMIT q->error(i18n("Could not create destination folder."));
0080             return false;
0081         }
0082 
0083         // Check if local and fast url. The check for fast url is needed because
0084         // otherwise the retrieved date will not be correct: see implementation
0085         // of TimeUtils::dateTimeForFileItem
0086         if (UrlUtils::urlIsFastLocalFile(url)) {
0087             QString tempDirPath = url.toLocalFile() + "/.gwenview_importer-XXXXXX";
0088             mTempImportDir = new QTemporaryDir(tempDirPath);
0089         } else {
0090             mTempImportDir = new QTemporaryDir();
0091         }
0092 
0093         if (!mTempImportDir->isValid()) {
0094             Q_EMIT q->error(i18n("Could not create temporary upload folder."));
0095             return false;
0096         }
0097 
0098         mTempImportDirUrl = QUrl::fromLocalFile(mTempImportDir->path() + QLatin1Char('/'));
0099         if (!mTempImportDirUrl.isValid()) {
0100             Q_EMIT q->error(i18n("Could not create temporary upload folder."));
0101             return false;
0102         }
0103 
0104         return true;
0105     }
0106 
0107     void importNext()
0108     {
0109         if (mUrlList.empty()) {
0110             q->finalizeImport();
0111             return;
0112         }
0113         mCurrentUrl = mUrlList.takeFirst();
0114         QUrl dst = mTempImportDirUrl;
0115         dst.setPath(dst.path() + mCurrentUrl.fileName());
0116         KIO::Job *job = KIO::copy(mCurrentUrl, dst, KIO::HideProgressInfo | KIO::Overwrite);
0117         KJobWidgets::setWindow(job, mAuthWindow);
0118         QObject::connect(job, &KJob::result, q, &Importer::slotCopyDone);
0119         QObject::connect(job, SIGNAL(percent(KJob *, ulong)), q, SLOT(slotPercent(KJob *, ulong)));
0120     }
0121 
0122     void renameImportedUrl(const QUrl &src)
0123     {
0124         QUrl dst = mDestinationDirUrl;
0125         QString fileName;
0126         if (mFileNameFormater.get()) {
0127             KFileItem item(src);
0128             item.setDelayedMimeTypes(true);
0129             // Get the document time, but do not cache the result because the
0130             // 'src' url is temporary: if we import "foo/image.jpg" and
0131             // "bar/image.jpg", both images will be temporarily saved in the
0132             // 'src' url.
0133             const QDateTime dateTime = TimeUtils::dateTimeForFileItem(item, TimeUtils::SkipCache);
0134             fileName = mFileNameFormater->format(src, dateTime);
0135         } else {
0136             fileName = src.fileName();
0137         }
0138         dst.setPath(dst.path() + QLatin1Char('/') + fileName);
0139 
0140         FileUtils::RenameResult result;
0141         // Create additional subfolders if needed (e.g. when extra slashes in FileNameFormater)
0142         QUrl subFolder = dst.adjusted(QUrl::RemoveFilename);
0143         KIO::Job *job = KIO::mkpath(subFolder, QUrl(), KIO::HideProgressInfo);
0144         KJobWidgets::setWindow(job, mAuthWindow);
0145         if (!job->exec()) { // if subfolder creation fails
0146             qCWarning(GWENVIEW_IMPORTER_LOG) << "Could not create subfolder:" << subFolder;
0147             if (!mFailedSubFolderList.contains(subFolder)) {
0148                 mFailedSubFolderList << subFolder;
0149             }
0150             result = FileUtils::RenameFailed;
0151         } else { // if subfolder creation succeeds
0152             result = FileUtils::rename(src, dst, mAuthWindow);
0153         }
0154 
0155         switch (result) {
0156         case FileUtils::RenamedOK:
0157             mImportedUrlList << mCurrentUrl;
0158             break;
0159         case FileUtils::RenamedUnderNewName:
0160             mRenamedCount++;
0161             mImportedUrlList << mCurrentUrl;
0162             break;
0163         case FileUtils::Skipped:
0164             mSkippedUrlList << mCurrentUrl;
0165             break;
0166         case FileUtils::RenameFailed:
0167             mFailedUrlList << mCurrentUrl;
0168             qCWarning(GWENVIEW_IMPORTER_LOG) << "Rename failed for" << mCurrentUrl;
0169         }
0170         q->advance();
0171         importNext();
0172     }
0173 };
0174 
0175 Importer::Importer(QWidget *parent)
0176     : QObject(parent)
0177     , d(new ImporterPrivate)
0178 {
0179     d->q = this;
0180     d->mAuthWindow = parent;
0181 }
0182 
0183 Importer::~Importer()
0184 {
0185     delete d;
0186 }
0187 
0188 void Importer::setAutoRenameFormat(const QString &format)
0189 {
0190     if (format.isEmpty()) {
0191         d->mFileNameFormater.reset(nullptr);
0192     } else {
0193         d->mFileNameFormater = std::make_unique<FileNameFormater>(format);
0194     }
0195 }
0196 
0197 void Importer::start(const QList<QUrl> &list, const QUrl &destination)
0198 {
0199     d->mDestinationDirUrl = destination;
0200     d->mUrlList = list;
0201     d->mImportedUrlList.clear();
0202     d->mSkippedUrlList.clear();
0203     d->mFailedUrlList.clear();
0204     d->mFailedSubFolderList.clear();
0205     d->mRenamedCount = 0;
0206     d->mProgress = 0;
0207     d->mJobProgress = 0;
0208 
0209     emitProgressChanged();
0210     Q_EMIT maximumChanged(d->mUrlList.count() * 100);
0211 
0212     if (!d->createImportDir(destination)) {
0213         qCWarning(GWENVIEW_IMPORTER_LOG) << "Could not create import dir";
0214         return;
0215     }
0216     d->importNext();
0217 }
0218 
0219 void Importer::slotCopyDone(KJob *_job)
0220 {
0221     auto job = static_cast<KIO::CopyJob *>(_job);
0222     const QUrl url = job->destUrl();
0223     if (job->error()) {
0224         // Add document to failed url list and proceed with next one
0225         d->mFailedUrlList << d->mCurrentUrl;
0226         advance();
0227         d->importNext();
0228         return;
0229     }
0230 
0231     d->renameImportedUrl(url);
0232 }
0233 
0234 void Importer::finalizeImport()
0235 {
0236     delete d->mTempImportDir;
0237     Q_EMIT importFinished();
0238 }
0239 
0240 void Importer::advance()
0241 {
0242     ++d->mProgress;
0243     d->mJobProgress = 0;
0244     emitProgressChanged();
0245 }
0246 
0247 void Importer::slotPercent(KJob *, unsigned long percent)
0248 {
0249     d->mJobProgress = percent;
0250     emitProgressChanged();
0251 }
0252 
0253 void Importer::emitProgressChanged()
0254 {
0255     Q_EMIT progressChanged(d->mProgress * 100 + d->mJobProgress);
0256 }
0257 
0258 QList<QUrl> Importer::importedUrlList() const
0259 {
0260     return d->mImportedUrlList;
0261 }
0262 
0263 QList<QUrl> Importer::skippedUrlList() const
0264 {
0265     return d->mSkippedUrlList;
0266 }
0267 
0268 QList<QUrl> Importer::failedUrlList() const
0269 {
0270     return d->mFailedUrlList;
0271 }
0272 
0273 QList<QUrl> Importer::failedSubFolderList() const
0274 {
0275     return d->mFailedSubFolderList;
0276 }
0277 
0278 int Importer::renamedCount() const
0279 {
0280     return d->mRenamedCount;
0281 }
0282 
0283 } // namespace
0284 
0285 #include "moc_importer.cpp"