diff -Nru rclone-browser-1.1/.appveyor.yml rclone-browser-1.2/.appveyor.yml --- rclone-browser-1.1/.appveyor.yml 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/.appveyor.yml 2017-03-11 22:16:36.000000000 +0000 @@ -7,7 +7,7 @@ build_script: - mkdir build - cd build - - cmake -G "Visual Studio 12 Win64" -DCMAKE_PREFIX_PATH=C:\Qt\5.7\msvc2013_64\lib\cmake .. + - cmake -G "Visual Studio 12 Win64" -DCMAKE_PREFIX_PATH=C:\Qt\5.8\msvc2013_64 .. - cmake --build . test: off deploy: off diff -Nru rclone-browser-1.1/CHANGELOG.md rclone-browser-1.2/CHANGELOG.md --- rclone-browser-1.1/CHANGELOG.md 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/CHANGELOG.md 2017-03-11 22:16:36.000000000 +0000 @@ -1,5 +1,16 @@ # Change Log +## [1.2] - 2017-03-11 +- Calculate size of folders, issue #4 +- Copy transfer command to clipboard, issue #20 +- Support custom .rclone.conf location, #21 +- Export list of files, issue #27 +- Bugfix for folder refresh not working after rename, issue #30 +- Remember empty text fields in transfer dialog, issue #32 +- Error message when too old rclone version is selected +- Support portable mode, issue #28 +- Create .deb packages, issue #26 + ## [1.1] - 2017-01-31 - Added `--transfer` option in UI, issue #1 - Supports encrypted `.rclone.conf` configuration file, issue #2 @@ -23,5 +34,6 @@ - Mount and unmount folders on macOS and GNU/Linux - Optionally minimizes to tray, with notifications when upload/download finishes +[1.2]: https://github.com/mmozeiko/RcloneBrowser/releases/tag/1.2 [1.1]: https://github.com/mmozeiko/RcloneBrowser/releases/tag/1.1 [1.0.0]: https://github.com/mmozeiko/RcloneBrowser/releases/tag/1.0.0 diff -Nru rclone-browser-1.1/CMakeLists.txt rclone-browser-1.2/CMakeLists.txt --- rclone-browser-1.1/CMakeLists.txt 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/CMakeLists.txt 2017-03-11 22:16:36.000000000 +0000 @@ -42,9 +42,7 @@ endif() -if(DEFINED ENV{RCLONE_BROWSER_VERSION}) - add_definitions("-DRCLONE_BROWSER_VERSION=\"$ENV{RCLONE_BROWSER_VERSION}\"") -endif() +file(READ "VERSION" RCLONE_BROWSER_VERSION) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${rclone-browser_BINARY_DIR}/build") diff -Nru rclone-browser-1.1/debian/changelog rclone-browser-1.2/debian/changelog --- rclone-browser-1.1/debian/changelog 2017-01-27 12:22:53.000000000 +0000 +++ rclone-browser-1.2/debian/changelog 2017-03-14 10:08:07.000000000 +0000 @@ -1,3 +1,10 @@ +rclone-browser (1.2-1~webupd8~vivid0) vivid; urgency=medium + + * New upstream release + * Disabled fix_strict-overflow_error.patch, applied upstream + + -- Alin Andrei Tue, 14 Mar 2017 11:19:39 +0100 + rclone-browser (1.1-1~webupd8~vivid0) vivid; urgency=medium * Added fix_strict-overflow_error.patch diff -Nru rclone-browser-1.1/.gitignore rclone-browser-1.2/.gitignore --- rclone-browser-1.1/.gitignore 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/.gitignore 2017-03-11 22:16:36.000000000 +0000 @@ -3,3 +3,8 @@ *.user* scripts/*.zip scripts/*.png +obj-*-linux-gnu +debian/files +debian/debhelper-build-stamp +debian/rclone-browser* + diff -Nru rclone-browser-1.1/README.md rclone-browser-1.2/README.md --- rclone-browser-1.1/README.md 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/README.md 2017-03-11 22:16:36.000000000 +0000 @@ -11,23 +11,30 @@ * Allows to browse and modify any rclone remote, including encrypted ones * Uses same configuration file as rclone, no extra configuration required -* Supports encrypted `.rclone.conf` configuration file +* Supports custom location and encryption for `.rclone.conf` configuration file * Simultaneously navigate multiple repositories in separate tabs * Lists files hierarchically with file name, size and modify date * All rclone commands are executed asynchronously, no freezing GUI * File hierarchy is lazily cached in memory, for faster traversal of folders * Allows to upload, download, create new folders, rename or delete files and folders +* Allows to calculate size of folder, export list of files and copy rclone copmmand to clipboard * Can process multiple upload or download jobs in background * Drag & drop support for dragging files from local file explorer for uploading * Streaming media files for playback in player like [mpv][6] or similar * Mount and unmount folders on macOS and GNU/Linux * Optionally minimizes to tray, with notifications when upload/download finishes +* Supports portable mode (create .ini file next to executable with same name), rclone and .rclone.conf path now can be relative to executable Download -------- -Get 64-bit Windows and macOS binary on [releases][3] page. -GNU/Linux users will need to build from source. ArchLinux users can install latest release from AUR repository [rclone-browser][7]. +Get Windows, macOS and Ubuntu package on [releases][3] page. + +For Ubuntu you can also install it from Launchpad: [Rclone Browser][launchpad]. + +ArchLinux users can install latest release from AUR repository: [rclone-browser][7]. + +Other GNU/Linux users will need to build from source. Screenshots ----------- @@ -94,3 +101,4 @@ [screenshot4]: https://raw.githubusercontent.com/wiki/mmozeiko/RcloneBrowser/screenshot4.png [screenshot5]: https://raw.githubusercontent.com/wiki/mmozeiko/RcloneBrowser/screenshot5.png [screenshot6]: https://raw.githubusercontent.com/wiki/mmozeiko/RcloneBrowser/screenshot6.png +[launchpad]: https://launchpad.net/~mmozeiko/+archive/ubuntu/rclone-browser diff -Nru rclone-browser-1.1/scripts/rclone-browser.desktop rclone-browser-1.2/scripts/rclone-browser.desktop --- rclone-browser-1.1/scripts/rclone-browser.desktop 1970-01-01 00:00:00.000000000 +0000 +++ rclone-browser-1.2/scripts/rclone-browser.desktop 2017-03-11 22:16:36.000000000 +0000 @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Rclone Browser +Comment=Simple cross-platform GUI for rclone +Exec=/usr/bin/rclone-browser +Icon=/usr/share/pixmaps/rclone-browser.png +Terminal=false +Type=Application +Categories=Network +StartupNotify=false diff -Nru rclone-browser-1.1/scripts/release_macOS.sh rclone-browser-1.2/scripts/release_macOS.sh --- rclone-browser-1.1/scripts/release_macOS.sh 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/scripts/release_macOS.sh 2017-03-11 22:16:36.000000000 +0000 @@ -4,15 +4,8 @@ QTDIR=~/Qt/5.8.0-desktop -if [ -z "$1" ] -then - echo "No version specified in cmdline!" - exit 1 -fi - -VERSION=$1-`git rev-parse --short HEAD` - ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"/.. +VERSION=`cat $ROOT/VERSION`-`git rev-parse --short HEAD` BUILD="$ROOT"/build TARGET=rclone-browser-$VERSION-macOS APP="$TARGET"/"Rclone Browser.app" @@ -20,7 +13,7 @@ rm -rf "$BUILD" mkdir -p "$BUILD" cd "$BUILD" -cmake .. -DCMAKE_PREFIX_PATH="$QTDIR" -DCMAKE_BUILD_TYPE=Release -DRCLONE_BROWSER_VERSION=$VERSION +cmake .. -DCMAKE_PREFIX_PATH="$QTDIR" -DCMAKE_BUILD_TYPE=Release make -j2 cd .. @@ -68,4 +61,9 @@ change "$APP"/Contents/Frameworks/QtPrintSupport.framework/QtPrintSupport "Core Gui Widgets" change "$APP"/Contents/Plugins/platforms/libqcocoa.dylib "Core Gui Widgets PrintSupport" +cat >"$APP"/Contents/MacOS/qt.conf <button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [=]() + { + ui.rbText->setChecked(true); + ui.checkVerbose->setChecked(false); + ui.checkSameFilesystem->setChecked(false); + ui.textMinSize->clear(); + ui.textMinAge->clear(); + ui.textMaxAge->clear(); + ui.spinMaxDepth->setValue(0); + ui.textExclude->clear(); + ui.textExtra->clear(); + }); + ui.buttonBox->button(QDialogButtonBox::RestoreDefaults)->click(); + + QObject::connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + QObject::connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + QObject::connect(ui.fileBrowse, &QToolButton::clicked, this, [=]() + { + QString file = QFileDialog::getSaveFileName(this, "Choose destination file"); + if (!file.isEmpty()) + { + ui.textFile->setText(QDir::toNativeSeparators(file)); + } + }); + + auto settings = GetSettings(); + settings->beginGroup("Export"); + ReadSettings(settings.get(), this); + settings->endGroup(); +} + +ExportDialog::~ExportDialog() +{ + if (result() == QDialog::Accepted) + { + auto settings = GetSettings(); + settings->beginGroup("Export"); + WriteSettings(settings.get(), this); + settings->remove("textFile"); + settings->endGroup(); + } +} + +QString ExportDialog::getDestination() const +{ + return ui.textFile->text(); +} + +bool ExportDialog::onlyFilenames() const +{ + return ui.rbText->isChecked(); +} + +QStringList ExportDialog::getOptions() const +{ + QStringList list; + list << "lsl"; + if (ui.checkVerbose->isChecked()) + { + list << "--verbose"; + } + if (ui.checkSameFilesystem->isChecked()) + { + list << "--one-file-system"; + } + if (!ui.textMinSize->text().isEmpty()) + { + list << "--min-size" << ui.textMinSize->text(); + } + if (!ui.textMinAge->text().isEmpty()) + { + list << "--min-age" << ui.textMinAge->text(); + } + if (!ui.textMaxAge->text().isEmpty()) + { + list << "--max-age" << ui.textMaxAge->text(); + } + if (ui.spinMaxDepth->value() != 0) + { + list << "--max-depth" << ui.spinMaxDepth->text(); + } + + QString excluded = ui.textExclude->toPlainText().trimmed(); + if (!excluded.isEmpty()) + { + for (auto line : excluded.split('\n')) + { + list << "--exclude" << line; + } + } + + QString extra = ui.textExtra->text().trimmed(); + if (!extra.isEmpty()) + { + for (auto arg : extra.split(' ')) + { + list << arg; + } + } + + list << mTarget; + + return list; +} + +void ExportDialog::done(int r) +{ + if (r == QDialog::Accepted) + { + if (ui.textFile->text().isEmpty()) + { + QMessageBox::warning(this, "Warning", "Please enter destination filename!"); + return; + } + } + QDialog::done(r); +} diff -Nru rclone-browser-1.1/src/export_dialog.h rclone-browser-1.2/src/export_dialog.h --- rclone-browser-1.1/src/export_dialog.h 1970-01-01 00:00:00.000000000 +0000 +++ rclone-browser-1.2/src/export_dialog.h 2017-03-11 22:16:36.000000000 +0000 @@ -0,0 +1,23 @@ +#pragma once + +#include "pch.h" +#include "ui_export_dialog.h" + +class ExportDialog : public QDialog +{ + Q_OBJECT + +public: + ExportDialog(const QString& remote, const QDir& path, QWidget* parent = nullptr); + ~ExportDialog(); + + QString getDestination() const; + bool onlyFilenames() const; + QStringList getOptions() const; + +private: + Ui::ExportDialog ui; + QString mTarget; + + void done(int r) override; +}; diff -Nru rclone-browser-1.1/src/export_dialog.ui rclone-browser-1.2/src/export_dialog.ui --- rclone-browser-1.1/src/export_dialog.ui 1970-01-01 00:00:00.000000000 +0000 +++ rclone-browser-1.2/src/export_dialog.ui 2017-03-11 22:16:36.000000000 +0000 @@ -0,0 +1,269 @@ + + + ExportDialog + + + + 0 + 0 + 614 + 358 + + + + + 0 + 0 + + + + + QLayout::SetFixedSize + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults + + + + + + + + + + + 0 + 0 + + + + File: + + + textFile + + + + + + + + + + ... + + + + + + + + + + 0 + + + + Settings + + + + + + Format + + + + 0 + + + + + + 0 + 0 + + + + Text (only filenames) + + + true + + + + + + + + 0 + 0 + + + + CSV (with size and datetime) + + + + + + + + + + Settings + + + + + + Extra arguments: + + + + + + + Maximum age (s or ms|s|m|h|d|w|M|y suffix) + + + textMaxAge + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999999 + + + + + + + Minimum age (s or ms|s|m|h|d|w|M|y suffix) + + + textMinAge + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + Minimum size (KiB or b|k|M|G suffix) + + + textMinSize + + + + + + + + 0 + 0 + + + + + + + + Maximum depth + + + spinMaxDepth + + + + + + + + + + Don't cross filesystem boundaries + + + + + + + Verbose output + + + + + + + + + + + Exclude + + + + + + QPlainTextEdit::NoWrap + + + + + + + + + + + textFile + fileBrowse + tabWidget + rbText + rbCSV + textMinSize + textMinAge + textMaxAge + spinMaxDepth + checkVerbose + checkSameFilesystem + textExtra + textExclude + + + + diff -Nru rclone-browser-1.1/src/item_model.cpp rclone-browser-1.2/src/item_model.cpp --- rclone-browser-1.1/src/item_model.cpp 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/item_model.cpp 2017-03-11 22:16:36.000000000 +0000 @@ -102,9 +102,9 @@ mFolderIcon = style->standardIcon(QStyle::SP_DirIcon); mFileIcon = style->standardIcon(QStyle::SP_FileIcon); - QSettings settings; - mFolderIcons = settings.value("Settings/showFolderIcons", true).toBool(); - mFileIcons = settings.value("Settings/showFileIcons", true).toBool(); + auto settings = GetSettings(); + mFolderIcons = settings->value("Settings/showFolderIcons", true).toBool(); + mFileIcons = settings->value("Settings/showFileIcons", true).toBool(); mRoot = new Item(); mRoot->isFolder = true; @@ -161,6 +161,7 @@ { Item* item = get(index); item->name = name; + item->path = item->parent->path.filePath(item->name); emit dataChanged(index, index, QVector{Qt::DisplayRole}); } @@ -610,8 +611,8 @@ timer->start(100); UseRclonePassword(lsd); UseRclonePassword(lsl); - lsd->start(GetRclone(), QStringList() << "lsd" << mRemote + ":" + parent->path.path(), QIODevice::ReadOnly); - lsl->start(GetRclone(), QStringList() << "lsl" << "--max-depth" << "1" << mRemote + ":" + parent->path.path(), QIODevice::ReadOnly); + lsd->start(GetRclone(), QStringList() << "lsd" << GetRcloneConf() << mRemote + ":" + parent->path.path(), QIODevice::ReadOnly); + lsl->start(GetRclone(), QStringList() << "lsl" << GetRcloneConf() << "--max-depth" << "1" << mRemote + ":" + parent->path.path(), QIODevice::ReadOnly); } void ItemModel::sortRecursive(Item* item, const ItemSorter& sorter) diff -Nru rclone-browser-1.1/src/job_widget.cpp rclone-browser-1.2/src/job_widget.cpp --- rclone-browser-1.1/src/job_widget.cpp 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/job_widget.cpp 2017-03-11 22:16:36.000000000 +0000 @@ -1,11 +1,16 @@ #include "job_widget.h" +#include "utils.h" -JobWidget::JobWidget(QProcess* process, const QString& info, const QString& source, const QString& dest, QWidget* parent) +JobWidget::JobWidget(QProcess* process, const QString& info, const QStringList& args, const QString& source, const QString& dest, QWidget* parent) : QWidget(parent) , mProcess(process) { ui.setupUi(this); + mArgs.append(QDir::toNativeSeparators(GetRclone())); + mArgs.append(GetRcloneConf()); + mArgs.append(args); + ui.source->setText(source); ui.dest->setText(dest); ui.info->setText(info); @@ -49,6 +54,14 @@ } }); + ui.copy->setIcon(QApplication::style()->standardIcon(QStyle::SP_FileLinkIcon)); + + QObject::connect(ui.copy, &QToolButton::clicked, this, [=]() + { + QClipboard* clipboard = QGuiApplication::clipboard(); + clipboard->setText(mArgs.join(" ")); + }); + QObject::connect(mProcess, &QProcess::readyRead, this, [=]() { QRegExp rxSize(R"(^Transferred:\s+(\S+ \S+) \(([^)]+)\)$)"); diff -Nru rclone-browser-1.1/src/job_widget.h rclone-browser-1.2/src/job_widget.h --- rclone-browser-1.1/src/job_widget.h 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/job_widget.h 2017-03-11 22:16:36.000000000 +0000 @@ -8,7 +8,7 @@ Q_OBJECT public: - JobWidget(QProcess* process, const QString& info, const QString& source, const QString& dest, QWidget* parent = nullptr); + JobWidget(QProcess* process, const QString& info, const QStringList& args, const QString& source, const QString& dest, QWidget* parent = nullptr); ~JobWidget(); void showDetails(); @@ -27,6 +27,7 @@ QProcess* mProcess; int mLines = 0; + QStringList mArgs; QHash mActive; QSet mUpdated; }; diff -Nru rclone-browser-1.1/src/job_widget.ui rclone-browser-1.2/src/job_widget.ui --- rclone-browser-1.1/src/job_widget.ui 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/job_widget.ui 2017-03-11 22:16:36.000000000 +0000 @@ -52,6 +52,16 @@ + + + Copy command to clipboard + + + QToolButton { border: 0 } + + + + Cancel @@ -59,9 +69,6 @@ QToolButton { border: 0 } - - - diff -Nru rclone-browser-1.1/src/main_window.cpp rclone-browser-1.2/src/main_window.cpp --- rclone-browser-1.1/src/main_window.cpp 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/main_window.cpp 2017-03-11 22:16:36.000000000 +0000 @@ -15,16 +15,17 @@ mSystemTray.setIcon(qApp->windowIcon()); { - QSettings settings; - if (settings.contains("MainWindow/geometry")) + auto settings = GetSettings(); + if (settings->contains("MainWindow/geometry")) { - restoreGeometry(settings.value("MainWindow/geometry").toByteArray()); + restoreGeometry(settings->value("MainWindow/geometry").toByteArray()); } - SetRclone(settings.value("Settings/rclone").toString()); + SetRclone(settings->value("Settings/rclone").toString()); + SetRcloneConf(settings->value("Settings/rcloneConf").toString()); - mAlwaysShowInTray = settings.value("Settings/alwaysShowInTray", false).toBool(); - mCloseToTray = settings.value("Settings/closeToTray", false).toBool(); - mNotifyFinishedTransfers = settings.value("Settings/notifyFinishedTransfers", true).toBool(); + mAlwaysShowInTray = settings->value("Settings/alwaysShowInTray", false).toBool(); + mCloseToTray = settings->value("Settings/closeToTray", false).toBool(); + mNotifyFinishedTransfers = settings->value("Settings/notifyFinishedTransfers", true).toBool(); mSystemTray.setVisible(mAlwaysShowInTray); } @@ -34,19 +35,21 @@ PreferencesDialog dialog(this); if (dialog.exec() == QDialog::Accepted) { - QSettings settings; - settings.setValue("Settings/rclone", dialog.getRclone()); - settings.setValue("Settings/stream", dialog.getStream()); + auto settings = GetSettings(); + settings->setValue("Settings/rclone", dialog.getRclone().trimmed()); + settings->setValue("Settings/rcloneConf", dialog.getRcloneConf().trimmed()); + settings->setValue("Settings/stream", dialog.getStream()); #ifndef Q_OS_WIN32 - settings.setValue("Settings/mount", dialog.getMount()); + settings->setValue("Settings/mount", dialog.getMount()); #endif - settings.setValue("Settings/alwaysShowInTray", dialog.getAlwaysShowInTray()); - settings.setValue("Settings/closeToTray", dialog.getCloseToTray()); - settings.setValue("Settings/notifyFinishedTransfers", dialog.getNotifyFinishedTransfers()); - settings.setValue("Settings/showFolderIcons", dialog.getShowFolderIcons()); - settings.setValue("Settings/showFileIcons", dialog.getShowFileIcons()); - settings.setValue("Settings/rowColors", dialog.getRowColors()); + settings->setValue("Settings/alwaysShowInTray", dialog.getAlwaysShowInTray()); + settings->setValue("Settings/closeToTray", dialog.getCloseToTray()); + settings->setValue("Settings/notifyFinishedTransfers", dialog.getNotifyFinishedTransfers()); + settings->setValue("Settings/showFolderIcons", dialog.getShowFolderIcons()); + settings->setValue("Settings/showFileIcons", dialog.getShowFileIcons()); + settings->setValue("Settings/rowColors", dialog.getRowColors()); SetRclone(dialog.getRclone()); + SetRcloneConf(dialog.getRcloneConf()); mFirstTime = true; rcloneGetVersion(); @@ -162,8 +165,8 @@ if (rclone.isEmpty()) { rclone = QStandardPaths::findExecutable("rclone"); - QSettings settings; - settings.setValue("Settings/rclone", rclone); + auto settings = GetSettings(); + settings->setValue("Settings/rclone", rclone); SetRclone(rclone); } if (rclone.isEmpty()) @@ -179,8 +182,8 @@ MainWindow::~MainWindow() { - QSettings settings; - settings.setValue("MainWindow/geometry", saveGeometry()); + auto settings = GetSettings(); + settings->setValue("MainWindow/geometry", saveGeometry()); } void MainWindow::rcloneGetVersion() @@ -254,11 +257,11 @@ args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES; }); p->setProgram(GetRclone()); - p->setArguments(QStringList() << "config"); + p->setArguments(QStringList() << "config" << GetRcloneConf()); #elif defined(Q_OS_OSX) auto tmp = new QFile("/tmp/rclone_config.command"); tmp->open(QIODevice::WriteOnly); - QTextStream(tmp) << "#!/bin/sh\n" << GetRclone() << " config" << "\n"; + QTextStream(tmp) << "#!/bin/sh\n" << GetRclone() << " config" << GetRcloneConf().join(" ") << "\n"; tmp->close(); tmp->setPermissions( QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ExeUser | @@ -282,7 +285,7 @@ } p->setProgram(terminal); - p->setArguments(QStringList() << "-e" << (GetRclone() + " config")); + p->setArguments(QStringList() << "-e" << (GetRclone() + " config" + GetRcloneConf().join(" "))); #endif UseRclonePassword(p); p->start(QIODevice::NotOpen); @@ -344,13 +347,12 @@ }); UseRclonePassword(p); - p->start(GetRclone(), QStringList() << "listremotes" << "-l" << "--ask-password=false", QIODevice::ReadOnly); + p->start(GetRclone(), QStringList() << "listremotes" << GetRcloneConf() << "-l" << "--ask-password=false", QIODevice::ReadOnly); } bool MainWindow::getConfigPassword(QProcess* p) { QString output = p->readAllStandardError().trimmed(); - qDebug() << output; if (output.indexOf("RCLONE_CONFIG_PASS") > 0) { bool ok; @@ -364,6 +366,11 @@ return true; } } + else if (output.indexOf("unknown command \"listremotes\"") > 0) + { + QMessageBox::critical(this, qApp->applicationDisplayName(), "It seems rclone version you are using is too old.\nPlease upgrade to at least version 1.34!"); + return false; + } return false; } @@ -443,7 +450,7 @@ QProcess* transfer = new QProcess(this); transfer->setProcessChannelMode(QProcess::MergedChannels); - auto widget = new JobWidget(transfer, message, source, dest); + auto widget = new JobWidget(transfer, message, args, source, dest); auto line = new QFrame(); line->setFrameShape(QFrame::HLine); @@ -494,7 +501,7 @@ ui.tabs->setTabText(1, QString("Jobs (%1)").arg(++mJobCount)); UseRclonePassword(transfer); - transfer->start(GetRclone(), args, QIODevice::ReadOnly); + transfer->start(GetRclone(), GetRcloneConf() + args, QIODevice::ReadOnly); } void MainWindow::addMount(const QString& remote, const QString& folder) @@ -541,19 +548,20 @@ ui.jobs->insertWidget(1, line); ui.tabs->setTabText(1, QString("Jobs (%1)").arg(++mJobCount)); - QSettings settings; - QString opt = settings.value("Settings/mount").toString(); + auto settings = GetSettings(); + QString opt = settings->value("Settings/mount").toString(); - QStringList options; - options << "mount"; + QStringList args; + args << "mount"; + args.append(GetRcloneConf()); if (!opt.isEmpty()) { - options.append(opt.split(' ')); + args.append(opt.split(' ')); } - options << remote << folder; + args << remote << folder; UseRclonePassword(mount); - mount->start(GetRclone(), options, QIODevice::ReadOnly); + mount->start(GetRclone(), args, QIODevice::ReadOnly); } void MainWindow::addStream(const QString& remote, const QString& stream) @@ -568,8 +576,8 @@ if (status != 0 && player->error() == QProcess::FailedToStart) { QMessageBox::critical(this, "Error", QString("Failed to start '%1' player process").arg(stream)); - QSettings settings; - settings.remove("Settings/streamConfirmed"); + auto settings = GetSettings(); + settings->remove("Settings/streamConfirmed"); } }); @@ -614,5 +622,5 @@ player->start(stream, QProcess::ReadOnly); UseRclonePassword(rclone); - rclone->start(GetRclone(), QStringList() << "cat" << remote, QProcess::WriteOnly); + rclone->start(GetRclone(), QStringList() << "cat" << GetRcloneConf() << remote, QProcess::WriteOnly); } diff -Nru rclone-browser-1.1/src/pch.h rclone-browser-1.2/src/pch.h --- rclone-browser-1.1/src/pch.h 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/pch.h 2017-03-11 22:16:36.000000000 +0000 @@ -4,6 +4,8 @@ #pragma warning(push, 0) #endif +#include + #include #include #include diff -Nru rclone-browser-1.1/src/preferences_dialog.cpp rclone-browser-1.2/src/preferences_dialog.cpp --- rclone-browser-1.1/src/preferences_dialog.cpp 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/preferences_dialog.cpp 2017-03-11 22:16:36.000000000 +0000 @@ -29,15 +29,27 @@ ui.rclone->setText(rclone); }); - QSettings settings; - ui.rclone->setText(QDir::toNativeSeparators(settings.value("Settings/rclone").toString())); - ui.stream->setText(settings.value("Settings/stream").toString()); - ui.showFolderIcons->setChecked(settings.value("Settings/showFolderIcons", true).toBool()); + QObject::connect(ui.rcloneConfBrowse, &QPushButton::clicked, this, [=]() + { + QString rcloneConf = QFileDialog::getOpenFileName(this, "Select .rclone.conf location", ui.rcloneConf->text()); + if (rcloneConf.isEmpty()) + { + return; + } + + ui.rcloneConf->setText(rcloneConf); + }); + + auto settings = GetSettings(); + ui.rclone->setText(QDir::toNativeSeparators(settings->value("Settings/rclone").toString())); + ui.rcloneConf->setText(QDir::toNativeSeparators(settings->value("Settings/rcloneConf").toString())); + ui.stream->setText(settings->value("Settings/stream").toString()); + ui.showFolderIcons->setChecked(settings->value("Settings/showFolderIcons", true).toBool()); if (QSystemTrayIcon::isSystemTrayAvailable()) { - ui.alwaysShowInTray->setChecked(settings.value("Settings/alwaysShowInTray", false).toBool()); - ui.closeToTray->setChecked(settings.value("Settings/closeToTray", false).toBool()); - ui.notifyFinishedTransfers->setChecked(settings.value("Settings/notifyFinishedTransfers", true).toBool()); + ui.alwaysShowInTray->setChecked(settings->value("Settings/alwaysShowInTray", false).toBool()); + ui.closeToTray->setChecked(settings->value("Settings/closeToTray", false).toBool()); + ui.notifyFinishedTransfers->setChecked(settings->value("Settings/notifyFinishedTransfers", true).toBool()); } else { @@ -48,14 +60,14 @@ ui.notifyFinishedTransfers->setChecked(false); ui.notifyFinishedTransfers->setDisabled(true); } - ui.showFileIcons->setChecked(settings.value("Settings/showFileIcons", true).toBool()); - ui.rowColors->setChecked(settings.value("Settings/rowColors", false).toBool()); + ui.showFileIcons->setChecked(settings->value("Settings/showFileIcons", true).toBool()); + ui.rowColors->setChecked(settings->value("Settings/rowColors", false).toBool()); #ifdef Q_OS_WIN32 ui.mount->hide(); ui.mountLabel->hide(); #else - ui.mount->setText(settings.value("Settings/mount").toString()); + ui.mount->setText(settings->value("Settings/mount").toString()); #endif } @@ -68,6 +80,11 @@ return QDir::fromNativeSeparators(ui.rclone->text()); } +QString PreferencesDialog::getRcloneConf() const +{ + return QDir::fromNativeSeparators(ui.rcloneConf->text()); +} + QString PreferencesDialog::getStream() const { return ui.stream->text(); diff -Nru rclone-browser-1.1/src/preferences_dialog.h rclone-browser-1.2/src/preferences_dialog.h --- rclone-browser-1.1/src/preferences_dialog.h 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/preferences_dialog.h 2017-03-11 22:16:36.000000000 +0000 @@ -12,6 +12,7 @@ ~PreferencesDialog(); QString getRclone() const; + QString getRcloneConf() const; QString getStream() const; QString getMount() const; diff -Nru rclone-browser-1.1/src/preferences_dialog.ui rclone-browser-1.2/src/preferences_dialog.ui --- rclone-browser-1.1/src/preferences_dialog.ui 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/preferences_dialog.ui 2017-03-11 22:16:36.000000000 +0000 @@ -7,7 +7,7 @@ 0 0 475 - 381 + 407 @@ -20,6 +20,13 @@ Settings + + + + ... + + + @@ -30,20 +37,16 @@ - - - - ... - - - - + + + + - + Mount options: @@ -53,10 +56,7 @@ - - - - + Stream command: @@ -66,6 +66,23 @@ + + + + .rclone.conf location: + + + + + + + + + + ... + + + @@ -182,6 +199,8 @@ rclone rcloneBrowse + rcloneConf + rcloneConfBrowse stream mount alwaysShowInTray diff -Nru rclone-browser-1.1/src/progress_dialog.cpp rclone-browser-1.2/src/progress_dialog.cpp --- rclone-browser-1.1/src/progress_dialog.cpp 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/progress_dialog.cpp 2017-03-11 22:16:36.000000000 +0000 @@ -1,6 +1,6 @@ #include "progress_dialog.h" -ProgressDialog::ProgressDialog(const QString& title, const QString& operation, const QString& message, QProcess* process, QWidget* parent) +ProgressDialog::ProgressDialog(const QString& title, const QString& operation, const QString& message, QProcess* process, QWidget* parent, bool close) : QDialog(parent) { ui.setupUi(this); @@ -30,7 +30,10 @@ { if (status == QProcess::NormalExit && code == 0) { - emit accept(); + if (close) + { + emit accept(); + } } else { @@ -41,7 +44,9 @@ QObject::connect(process, &QProcess::readyRead, this, [=]() { - ui.output->appendPlainText(process->readAll()); + QString output = process->readAll(); + ui.output->appendPlainText(output); + emit outputAvailable(output); }); process->setProcessChannelMode(QProcess::MergedChannels); @@ -51,3 +56,18 @@ ProgressDialog::~ProgressDialog() { } + +void ProgressDialog::expand() +{ + ui.buttonShowOutput->setChecked(true); +} + +void ProgressDialog::allowToClose() +{ + ui.buttonBox->setEnabled(true); +} +// +//QString ProgressDialog::getOutput() const +//{ +// return ui.output->toPlainText(); +//} diff -Nru rclone-browser-1.1/src/progress_dialog.h rclone-browser-1.2/src/progress_dialog.h --- rclone-browser-1.1/src/progress_dialog.h 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/progress_dialog.h 2017-03-11 22:16:36.000000000 +0000 @@ -8,9 +8,15 @@ Q_OBJECT public: - ProgressDialog(const QString& title, const QString& operation, const QString& message, QProcess* process, QWidget* parent = nullptr); + ProgressDialog(const QString& title, const QString& operation, const QString& message, QProcess* process, QWidget* parent = nullptr, bool close = true); ~ProgressDialog(); + void expand(); + void allowToClose(); + +signals: + void outputAvailable(const QString& output) const; + private: Ui::ProgressDialog ui; }; diff -Nru rclone-browser-1.1/src/progress_dialog.ui rclone-browser-1.2/src/progress_dialog.ui --- rclone-browser-1.1/src/progress_dialog.ui 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/progress_dialog.ui 2017-03-11 22:16:36.000000000 +0000 @@ -6,8 +6,8 @@ 0 0 - 621 - 311 + 618 + 291 diff -Nru rclone-browser-1.1/src/remote_widget.cpp rclone-browser-1.2/src/remote_widget.cpp --- rclone-browser-1.1/src/remote_widget.cpp 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/remote_widget.cpp 2017-03-11 22:16:36.000000000 +0000 @@ -1,5 +1,6 @@ #include "remote_widget.h" #include "transfer_dialog.h" +#include "export_dialog.h" #include "progress_dialog.h" #include "icon_cache.h" #include "item_model.h" @@ -18,8 +19,8 @@ #else isLocal = false; #endif - QSettings settings; - ui.tree->setAlternatingRowColors(settings.value("Settings/rowColors", false).toBool()); + auto settings = GetSettings(); + ui.tree->setAlternatingRowColors(settings->value("Settings/rowColors", false).toBool()); QStyle* style = QApplication::style(); ui.refresh->setIcon(style->standardIcon(QStyle::SP_BrowserReload)); @@ -30,6 +31,9 @@ ui.stream->setIcon(style->standardIcon(QStyle::SP_MediaPlay)); ui.upload->setIcon(style->standardIcon(QStyle::SP_ArrowUp)); ui.download->setIcon(style->standardIcon(QStyle::SP_ArrowDown)); + ui.download->setIcon(style->standardIcon(QStyle::SP_ArrowDown)); + ui.getSize->setIcon(style->standardIcon(QStyle::SP_FileDialogInfoView)); + ui.export_->setIcon(style->standardIcon(QStyle::SP_FileDialogDetailedView)); ui.buttonRefresh->setDefaultAction(ui.refresh); ui.buttonMkdir->setDefaultAction(ui.mkdir); @@ -39,6 +43,8 @@ ui.buttonStream->setDefaultAction(ui.stream); ui.buttonUpload->setDefaultAction(ui.upload); ui.buttonDownload->setDefaultAction(ui.download); + ui.buttonSize->setDefaultAction(ui.getSize); + ui.buttonExport->setDefaultAction(ui.export_); ui.tree->sortByColumn(0, Qt::AscendingOrder); ui.tree->header()->setSectionsMovable(false); @@ -92,6 +98,8 @@ path = model->path(index); } + ui.getSize->setDisabled(!isFolder); + ui.export_->setDisabled(!isFolder); ui.path->setText(isLocal ? QDir::toNativeSeparators(path.path()) : path.path()); }); @@ -120,7 +128,7 @@ QProcess process; UseRclonePassword(&process); process.setProgram(GetRclone()); - process.setArguments(QStringList() << "mkdir" << remote + ":" + folder); + process.setArguments(QStringList() << "mkdir" << GetRcloneConf() << remote + ":" + folder); process.setReadChannelMode(QProcess::MergedChannels); ProgressDialog progress("New Folder", "Creating...", folderMsg, &process, this); @@ -147,6 +155,7 @@ process.setProgram(GetRclone()); process.setArguments(QStringList() << "move" + << GetRcloneConf() << remote + ":" + path << remote + ":" + model->path(index.parent()).filePath(name)); process.setReadChannelMode(QProcess::MergedChannels); @@ -172,7 +181,7 @@ QProcess process; UseRclonePassword(&process); process.setProgram(GetRclone()); - process.setArguments(QStringList() << (model->isFolder(index) ? "purge" : "delete") << remote + ":" + path); + process.setArguments(QStringList() << (model->isFolder(index) ? "purge" : "delete") << GetRcloneConf() << remote + ":" + path); process.setReadChannelMode(QProcess::MergedChannels); ProgressDialog progress("Delete", "Deleting...", pathMsg, &process, this); @@ -205,9 +214,9 @@ QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); QString path = model->path(index).path(); - QSettings settings; - bool streamConfirmed = settings.value("Settings/streamConfirmed", false).toBool(); - QString stream = settings.value("Settings/stream", "mpv -").toString(); + auto settings = GetSettings(); + bool streamConfirmed = settings->value("Settings/streamConfirmed", false).toBool(); + QString stream = settings->value("Settings/stream", "mpv -").toString(); if (!streamConfirmed) { QString result = QInputDialog::getText(this, "Stream", "Enter stream command (file will be passed in STDIN):", QLineEdit::Normal, stream); @@ -218,8 +227,8 @@ stream = result; - settings.setValue("Settings/stream", stream); - settings.setValue("Settings/streamConfirmed", true); + settings->setValue("Settings/stream", stream); + settings->setValue("Settings/streamConfirmed", true); } emit addStream(remote + ":" + path, stream); @@ -261,6 +270,86 @@ } }); + QObject::connect(ui.getSize, &QAction::triggered, this, [=]() + { + QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); + + QString path = model->path(index).path(); + QString pathMsg = isLocal ? QDir::toNativeSeparators(path) : path; + + QProcess process; + UseRclonePassword(&process); + process.setProgram(GetRclone()); + process.setArguments(QStringList() << "size" << GetRcloneConf() << remote + ":" + path); + process.setReadChannelMode(QProcess::MergedChannels); + + ProgressDialog progress("Get Size", "Calculating...", pathMsg, &process, this, false); + progress.expand(); + progress.allowToClose(); + progress.exec(); + }); + + QObject::connect(ui.export_, &QAction::triggered, this, [=]() + { + QModelIndex index = ui.tree->selectionModel()->selectedRows().front(); + QDir path = model->path(index); + + ExportDialog e(remote, path, this); + if (e.exec() == QDialog::Accepted) + { + QString dst = e.getDestination(); + bool txt = e.onlyFilenames(); + + QFile* file = new QFile(dst); + if (!file->open(QFile::WriteOnly)) + { + QMessageBox::warning(this, "Error", QString("Cannot open file '%1' for writing!").arg(dst)); + delete file; + return; + } + + QRegExp re(R"(^(\d+) (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\.\d+ (.+)$)"); + + QProcess process; + UseRclonePassword(&process); + process.setProgram(GetRclone()); + process.setArguments(QStringList() << GetRcloneConf() << e.getOptions()); + process.setReadChannelMode(QProcess::MergedChannels); + + ProgressDialog progress("Export", "Exporting...", dst, &process, this); + file->setParent(&progress); + + QObject::connect(&progress, &ProgressDialog::outputAvailable, this, [=](const QString& output) + { + QTextStream out(file); + + for (const auto& line : output.split('\n')) + { + if (re.exactMatch(line.trimmed())) + { + QStringList cap = re.capturedTexts(); + + if (txt) + { + out << cap[3] << '\n'; + } + else + { + QString name = cap[3]; + if (name.contains(' ') || name.contains(',') || name.contains('"')) + { + name = '"' + name.replace("\"", "\"\"") + '"'; + } + out << name << ',' << '"' << cap[2] << '"' << ',' << cap[1].toULongLong() << '\n'; + } + } + } + }); + + progress.exec(); + } + }); + QObject::connect(model, &ItemModel::drop, this, [=](const QDir& path, const QModelIndex& parent) { qApp->setActiveWindow(this); @@ -276,13 +365,14 @@ QStringList args = t.getOptions(); emit addTransfer(QString("%1 from %2").arg(t.getMode()).arg(src), src, dst, args); } - }); QObject::connect(ui.tree, &QWidget::customContextMenuRequested, this, [=](const QPoint& pos) { QMenu menu; menu.addAction(ui.refresh); + menu.addAction(ui.getSize); + menu.addAction(ui.export_); menu.addSeparator(); menu.addAction(ui.mkdir); menu.addAction(ui.rename); diff -Nru rclone-browser-1.1/src/remote_widget.ui rclone-browser-1.2/src/remote_widget.ui --- rclone-browser-1.1/src/remote_widget.ui 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/remote_widget.ui 2017-03-11 22:16:36.000000000 +0000 @@ -132,6 +132,26 @@ + + + &Get Size... + + + Qt::ToolButtonTextBesideIcon + + + + + + + E&xport... + + + Qt::ToolButtonTextBesideIcon + + + + Qt::Horizontal @@ -245,6 +265,16 @@ &Download + + + &Get Size + + + + + E&xport + + buttonRefresh @@ -255,6 +285,7 @@ buttonStream buttonUpload buttonDownload + buttonSize path tree diff -Nru rclone-browser-1.1/src/transfer_dialog.cpp rclone-browser-1.2/src/transfer_dialog.cpp --- rclone-browser-1.1/src/transfer_dialog.cpp 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/transfer_dialog.cpp 2017-03-11 22:16:36.000000000 +0000 @@ -86,10 +86,10 @@ } }); - QSettings settings; - settings.beginGroup("Transfer"); - ReadSettings(&settings, this); - settings.endGroup(); + auto settings = GetSettings(); + settings->beginGroup("Transfer"); + ReadSettings(settings.get(), this); + settings->endGroup(); ui.buttonSourceFile->setVisible(!isDownload); ui.buttonSourceFolder->setVisible(!isDownload); @@ -102,12 +102,12 @@ { if (result() == QDialog::Accepted) { - QSettings settings; - settings.beginGroup("Transfer"); - WriteSettings(&settings, this); - settings.remove("textSource"); - settings.remove("textDest"); - settings.endGroup(); + auto settings = GetSettings(); + settings->beginGroup("Transfer"); + WriteSettings(settings.get(), this); + settings->remove("textSource"); + settings->remove("textDest"); + settings->endGroup(); } } diff -Nru rclone-browser-1.1/src/utils.cpp rclone-browser-1.2/src/utils.cpp --- rclone-browser-1.1/src/utils.cpp 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/utils.cpp 2017-03-11 22:16:36.000000000 +0000 @@ -1,13 +1,40 @@ #include "utils.h" static QString gRclone; +static QString gRcloneConf; static QString gRclonePassword; +static QString GetIniFilename() +{ + QFileInfo info = qApp->applicationFilePath(); + return info.dir().filePath(info.baseName() + ".ini"); +} + +static bool IsPortableMode() +{ + QString ini = GetIniFilename(); + return QFileInfo(ini).exists(); +} + +std::unique_ptr GetSettings() +{ + if (IsPortableMode()) + { + return std::unique_ptr(new QSettings(GetIniFilename(), QSettings::IniFormat)); + } + return std::unique_ptr(new QSettings); +} + void ReadSettings(QSettings* settings, QObject* widget) { QString name = widget->objectName(); if (!name.isEmpty() && settings->contains(name)) { + if (QRadioButton* obj = qobject_cast(widget)) + { + obj->setChecked(settings->value(name).toBool()); + return; + } if (QCheckBox* obj = qobject_cast(widget)) { obj->setChecked(settings->value(name).toBool()); @@ -71,7 +98,11 @@ } if (QLineEdit* obj = qobject_cast(widget)) { - if (!obj->text().isEmpty()) + if (obj->text().isEmpty()) + { + settings->remove(name); + } + else { settings->setValue(name, obj->text()); } @@ -100,14 +131,40 @@ } } +QStringList GetRcloneConf() +{ + if (gRcloneConf.isEmpty()) + { + return QStringList(); + } + + QString conf = gRcloneConf; + if (IsPortableMode() && QFileInfo(conf).isRelative()) + { + conf = QDir(qApp->applicationDirPath()).filePath(conf); + } + return QStringList() << "--config" << conf; +} + +void SetRcloneConf(const QString& rcloneConf) +{ + gRcloneConf = rcloneConf; +} + QString GetRclone() { - return gRclone; + QString rclone = gRclone; + if (IsPortableMode() && QFileInfo(rclone).isRelative()) + { + rclone = QDir(qApp->applicationDirPath()).filePath(rclone); + } + + return rclone; } void SetRclone(const QString& rclone) { - gRclone = rclone; + gRclone = rclone.trimmed(); } void UseRclonePassword(QProcess* process) diff -Nru rclone-browser-1.1/src/utils.h rclone-browser-1.2/src/utils.h --- rclone-browser-1.1/src/utils.h 2017-02-01 05:53:19.000000000 +0000 +++ rclone-browser-1.2/src/utils.h 2017-03-11 22:16:36.000000000 +0000 @@ -2,11 +2,16 @@ #include "pch.h" +std::unique_ptr GetSettings(); + void ReadSettings(QSettings* settings, QObject* widget); void WriteSettings(QSettings* settings, QObject* widget); QString GetRclone(); void SetRclone(const QString& rclone); +QStringList GetRcloneConf(); +void SetRcloneConf(const QString& rcloneConf); + void UseRclonePassword(QProcess* process); void SetRclonePassword(const QString& rclonePassword); diff -Nru rclone-browser-1.1/VERSION rclone-browser-1.2/VERSION --- rclone-browser-1.1/VERSION 1970-01-01 00:00:00.000000000 +0000 +++ rclone-browser-1.2/VERSION 2017-03-11 22:16:36.000000000 +0000 @@ -0,0 +1 @@ +1.2 \ No newline at end of file