diff --git a/importer/fileutils.cpp b/importer/fileutils.cpp index 5293d561..a51a18cf 100644 --- a/importer/fileutils.cpp +++ b/importer/fileutils.cpp @@ -1,159 +1,162 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2009 Aurélien Gâteau 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, Cambridge, MA 02110-1301, USA. */ // Self #include "fileutils.h" // Qt #include #include #include // KDE #include #include #include #include #include #include #include // libc #include #include #include namespace Gwenview { namespace FileUtils { bool contentsAreIdentical(const QUrl& url1, const QUrl& url2, QWidget* authWindow) { // FIXME: Support remote urls KIO::StatJob *statJob = KIO::mostLocalUrl(url1); KJobWidgets::setWindow(statJob, authWindow); if (!statJob->exec()) { qWarning() << "Unable to stat" << url1; return false; } QFile file1(statJob->mostLocalUrl().toLocalFile()); if (!file1.open(QIODevice::ReadOnly)) { // Can't read url1, assume it's different from url2 qWarning() << "Can't read" << url1; return false; } statJob = KIO::mostLocalUrl(url2); KJobWidgets::setWindow(statJob, authWindow); if (!statJob->exec()) { qWarning() << "Unable to stat" << url2; return false; } QFile file2(statJob->mostLocalUrl().toLocalFile()); if (!file2.open(QIODevice::ReadOnly)) { // Can't read url2, assume it's different from url1 qWarning() << "Can't read" << url2; return false; } const int CHUNK_SIZE = 4096; while (!file1.atEnd() && !file2.atEnd()) { QByteArray url1Array = file1.read(CHUNK_SIZE); QByteArray url2Array = file2.read(CHUNK_SIZE); if (url1Array != url2Array) { return false; } } if (file1.atEnd() && file2.atEnd()) { return true; } else { qWarning() << "One file ended before the other"; return false; } } RenameResult rename(const QUrl& src, const QUrl& dst_, QWidget* authWindow) { QUrl dst = dst_; RenameResult result = RenamedOK; int count = 1; QFileInfo fileInfo(dst.fileName()); QString prefix = fileInfo.completeBaseName() + '_'; QString suffix = '.' + fileInfo.suffix(); // Get src size KIO::StatJob *sourceStat = KIO::stat(src); KJobWidgets::setWindow(sourceStat, authWindow); if (!sourceStat->exec()) { return RenameFailed; } KFileItem item(sourceStat->statResult(), src, true /* delayedMimeTypes */); KIO::filesize_t srcSize = item.size(); // Find unique name KIO::StatJob *statJob = KIO::stat(dst); KJobWidgets::setWindow(statJob, authWindow); while (statJob->exec()) { // File exists. If it's not the same, try to create a new name item = KFileItem(statJob->statResult(), dst, true /* delayedMimeTypes */); KIO::filesize_t dstSize = item.size(); if (srcSize == dstSize && contentsAreIdentical(src, dst, authWindow)) { // Already imported, skip it KIO::Job* job = KIO::file_delete(src, KIO::HideProgressInfo); KJobWidgets::setWindow(job, authWindow); return Skipped; } result = RenamedUnderNewName; - dst.setPath(dst.path() + '/' + prefix + QString::number(count) + suffix); + dst.setPath(dst.adjusted(QUrl::RemoveFilename).path() + prefix + QString::number(count) + suffix); + statJob = KIO::stat(dst); + KJobWidgets::setWindow(statJob, authWindow); + ++count; } // Rename KIO::Job* job = KIO::rename(src, dst, KIO::HideProgressInfo); KJobWidgets::setWindow(job, authWindow); if (!job->exec()) { result = RenameFailed; } return result; } QString createTempDir(const QString& baseDir, const QString& prefix, QString* errorMessage) { Q_ASSERT(errorMessage); QByteArray name = QFile::encodeName(baseDir + '/' + prefix + "XXXXXX"); if (!mkdtemp(name.data())) { // Failure *errorMessage = QString::fromLocal8Bit(::strerror(errno)); return QString(); } return QFile::decodeName(name + '/'); } } // namespace } // namespace diff --git a/importer/importdialog.cpp b/importer/importdialog.cpp index ee6f7cd6..5e9e847c 100644 --- a/importer/importdialog.cpp +++ b/importer/importdialog.cpp @@ -1,267 +1,267 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2009 Aurélien Gâteau 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, Cambridge, MA 02110-1301, USA. */ // Self #include "importdialog.h" // Qt #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include // Local #include "dialogpage.h" #include "importer.h" #include "importerconfig.h" #include "progresspage.h" #include "thumbnailpage.h" namespace Gwenview { class ImportDialogPrivate { public: ImportDialog* q; QStackedWidget* mCentralWidget; ThumbnailPage* mThumbnailPage; ProgressPage* mProgressPage; DialogPage* mDialogPage; Importer* mImporter; void deleteImportedUrls() { QList importedUrls = mImporter->importedUrlList(); QList skippedUrls = mImporter->skippedUrlList(); int importedCount = importedUrls.count(); int skippedCount = skippedUrls.count(); if (importedCount == 0 && skippedCount == 0) { return; } QStringList message; message << i18np( "One document has been imported.", "%1 documents have been imported.", importedCount); if (skippedCount > 0) { message << i18np( "One document has been skipped because it had already been imported.", "%1 documents have been skipped because they had already been imported.", skippedCount); } if (mImporter->renamedCount() > 0) { message[0].append("*"); message << "* " + i18np( "One of them has been renamed because another document with the same name had already been imported.", "%1 of them have been renamed because other documents with the same name had already been imported.", mImporter->renamedCount()) + ""; } message << QString(); if (skippedCount == 0) { message << i18np( "Delete the imported document from the device?", "Delete the %1 imported documents from the device?", importedCount); } else if (importedCount == 0) { message << i18np( "Delete the skipped document from the device?", "Delete the %1 skipped documents from the device?", skippedCount); } else { message << i18ncp( "Singular sentence is actually never used.", "Delete the imported or skipped document from the device?", "Delete the %1 imported and skipped documents from the device?", importedCount + skippedCount); } int answer = KMessageBox::questionYesNo(mCentralWidget, "" + message.join("
") + "
", i18nc("@title:window", "Import Finished"), KStandardGuiItem::del(), KGuiItem(i18n("Keep")) ); if (answer != KMessageBox::Yes) { return; } QList urls = importedUrls + skippedUrls; while (true) { KIO::Job* job = KIO::del(urls); - if (!job->exec()) { + if (job->exec()) { break; } // Deleting failed int answer = KMessageBox::warningYesNo(mCentralWidget, i18np("Failed to delete the document:\n%2", "Failed to delete documents:\n%2", urls.count(), job->errorString()), QString(), KGuiItem(i18n("Retry")), KGuiItem(i18n("Ignore")) ); if (answer != KMessageBox::Yes) { // Ignore break; } } } void startGwenview() { KService::Ptr service = KService::serviceByDesktopName("org.kde.gwenview"); if (!service) { qCritical() << "Could not find gwenview"; } else { KRun::run(*service, {mThumbnailPage->destinationUrl()}, 0 /* window */); } } void showWhatNext() { mCentralWidget->setCurrentWidget(mDialogPage); mDialogPage->setText(i18n("What do you want to do now?")); mDialogPage->removeButtons(); int gwenview = mDialogPage->addButton(KGuiItem(i18n("View Imported Documents with Gwenview"), "gwenview")); int importMore = mDialogPage->addButton(KGuiItem(i18n("Import more Documents"))); mDialogPage->addButton(KGuiItem(i18n("Quit"), "dialog-cancel")); int answer = mDialogPage->exec(); if (answer == gwenview) { startGwenview(); qApp->quit(); } else if (answer == importMore) { mCentralWidget->setCurrentWidget(mThumbnailPage); } else { /* quit */ qApp->quit(); } } }; ImportDialog::ImportDialog() : d(new ImportDialogPrivate) { d->q = this; d->mImporter = new Importer(this); connect(d->mImporter, SIGNAL(error(QString)), SLOT(showImportError(QString))); d->mThumbnailPage = new ThumbnailPage; QUrl url = ImporterConfig::destinationUrl(); if (!url.isValid()) { url = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); int year = QDate::currentDate().year(); url.setPath(url.path() + '/' + QString::number(year)); } d->mThumbnailPage->setDestinationUrl(url); d->mProgressPage = new ProgressPage(d->mImporter); d->mDialogPage = new DialogPage; d->mCentralWidget = new QStackedWidget; setCentralWidget(d->mCentralWidget); d->mCentralWidget->addWidget(d->mThumbnailPage); d->mCentralWidget->addWidget(d->mProgressPage); d->mCentralWidget->addWidget(d->mDialogPage); connect(d->mThumbnailPage, SIGNAL(importRequested()), SLOT(startImport())); connect(d->mThumbnailPage, SIGNAL(rejected()), SLOT(close())); connect(d->mImporter, SIGNAL(importFinished()), SLOT(slotImportFinished())); d->mCentralWidget->setCurrentWidget(d->mThumbnailPage); setWindowIcon(QIcon::fromTheme("gwenview")); setAutoSaveSettings(); } ImportDialog::~ImportDialog() { delete d; } QSize ImportDialog::sizeHint() const { return QSize(700, 500); } void ImportDialog::setSourceUrl(const QUrl& url, const QString& deviceUdi) { QString name, iconName; if (deviceUdi.isEmpty()) { name = url.url(QUrl::PreferLocalFile); iconName = KProtocolInfo::icon(url.scheme()); if (iconName.isEmpty()) { iconName = "folder"; } } else { Solid::Device device(deviceUdi); name = device.vendor() + " " + device.product(); iconName = device.icon(); } d->mThumbnailPage->setSourceUrl(url, iconName, name); } void ImportDialog::startImport() { QUrl url = d->mThumbnailPage->destinationUrl(); ImporterConfig::setDestinationUrl(url); ImporterConfig::self()->save(); d->mCentralWidget->setCurrentWidget(d->mProgressPage); d->mImporter->setAutoRenameFormat( ImporterConfig::autoRename() ? ImporterConfig::autoRenameFormat() : QString()); d->mImporter->start(d->mThumbnailPage->urlList(), url); } void ImportDialog::slotImportFinished() { d->deleteImportedUrls(); d->showWhatNext(); } void ImportDialog::showImportError(const QString& message) { KMessageBox::sorry(this, message); d->mCentralWidget->setCurrentWidget(d->mThumbnailPage); } } // namespace diff --git a/importer/importer.cpp b/importer/importer.cpp index 51c4b964..a7e1d4ea 100644 --- a/importer/importer.cpp +++ b/importer/importer.cpp @@ -1,242 +1,242 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2009 Aurélien Gâteau 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, Cambridge, MA 02110-1301, USA. */ // Self #include "importer.h" // Qt #include #include #include // KDE #include #include #include #include #include #include #include // stdc++ #include // Local #include #include #include #include namespace Gwenview { struct ImporterPrivate { Importer* q; QWidget* mAuthWindow; std::auto_ptr mFileNameFormater; QUrl mTempImportDirUrl; /* @defgroup reset Should be reset in start() * @{ */ QList mUrlList; QList mImportedUrlList; QList mSkippedUrlList; int mRenamedCount; int mProgress; int mJobProgress; /* @} */ QUrl mCurrentUrl; void emitError(const QString& message) { QMetaObject::invokeMethod(q, "error", Q_ARG(QString, message)); } bool createImportDir(const QUrl& url) { Q_ASSERT(url.isLocalFile()); // FIXME: Support remote urls if (!QDir().mkpath(url.toLocalFile())) { emitError(i18n("Could not create destination folder.")); return false; } QString message; QString dir = FileUtils::createTempDir(url.toLocalFile(), ".gwenview_importer-", &message); mTempImportDirUrl = QUrl::fromLocalFile(dir); if (mTempImportDirUrl.isEmpty()) { emitError(i18n("Could not create temporary upload folder:\n%1", message)); return false; } return true; } void importNext() { if (mUrlList.empty()) { q->finalizeImport(); return; } mCurrentUrl = mUrlList.takeFirst(); QUrl dst = mTempImportDirUrl; - dst.setPath(dst.path() + '/' + mCurrentUrl.fileName()); + dst.setPath(dst.path() + mCurrentUrl.fileName()); KIO::Job* job = KIO::copy(mCurrentUrl, dst, KIO::HideProgressInfo); KJobWidgets::setWindow(job, mAuthWindow); QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(slotCopyDone(KJob*))); QObject::connect(job, SIGNAL(percent(KJob*,ulong)), q, SLOT(slotPercent(KJob*,ulong))); } void renameImportedUrl(const QUrl& src) { QUrl dst = src.resolved(QUrl("..")); QString fileName; if (mFileNameFormater.get()) { KFileItem item(KFileItem::Unknown, KFileItem::Unknown, src, true /* delayedMimeTypes */); // Get the document time, but do not cache the result because the // 'src' url is temporary: if we import "foo/image.jpg" and // "bar/image.jpg", both images will be temporarily saved in the // 'src' url. QDateTime dateTime = TimeUtils::dateTimeForFileItem(item, TimeUtils::SkipCache); fileName = mFileNameFormater->format(src, dateTime); } else { fileName = src.fileName(); } - dst.setPath(dst.path() + '/' + fileName); + dst.setPath(dst.path() + fileName); FileUtils::RenameResult result = FileUtils::rename(src, dst, mAuthWindow); switch (result) { case FileUtils::RenamedOK: mImportedUrlList << mCurrentUrl; break; case FileUtils::RenamedUnderNewName: mRenamedCount++; mImportedUrlList << mCurrentUrl; break; case FileUtils::Skipped: mSkippedUrlList << mCurrentUrl; break; case FileUtils::RenameFailed: qWarning() << "Rename failed for" << mCurrentUrl; } q->advance(); importNext(); } }; Importer::Importer(QWidget* parent) : QObject(parent) , d(new ImporterPrivate) { d->q = this; d->mAuthWindow = parent; } Importer::~Importer() { delete d; } void Importer::setAutoRenameFormat(const QString& format) { if (format.isEmpty()) { d->mFileNameFormater.reset(0); } else { d->mFileNameFormater.reset(new FileNameFormater(format)); } } void Importer::start(const QList& list, const QUrl& destination) { d->mUrlList = list; d->mImportedUrlList.clear(); d->mSkippedUrlList.clear(); d->mRenamedCount = 0; d->mProgress = 0; d->mJobProgress = 0; emitProgressChanged(); maximumChanged(d->mUrlList.count() * 100); if (!d->createImportDir(destination)) { qWarning() << "Could not create import dir"; return; } d->importNext(); } void Importer::slotCopyDone(KJob* _job) { KIO::CopyJob* job = static_cast(_job); QUrl url = job->destUrl(); if (job->error()) { qWarning() << "FIXME: What do we do with failed urls?"; advance(); d->importNext(); return; } d->renameImportedUrl(url); } void Importer::finalizeImport() { KIO::Job* job = KIO::del(d->mTempImportDirUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(job, d->mAuthWindow); importFinished(); } void Importer::advance() { ++d->mProgress; d->mJobProgress = 0; emitProgressChanged(); } void Importer::slotPercent(KJob*, unsigned long percent) { d->mJobProgress = percent; emitProgressChanged(); } void Importer::emitProgressChanged() { progressChanged(d->mProgress * 100 + d->mJobProgress); } QList Importer::importedUrlList() const { return d->mImportedUrlList; } QList Importer::skippedUrlList() const { return d->mSkippedUrlList; } int Importer::renamedCount() const { return d->mRenamedCount; } } // namespace