set(client_UI_SRCS
accountsettings.ui
conflictdialog.ui
+ foldercreationdialog.ui
folderwizardsourcepage.ui
folderwizardtargetpage.ui
generalsettings.ui
conflictsolver.cpp
connectionvalidator.cpp
folder.cpp
+ foldercreationdialog.cpp
folderman.cpp
folderstatusmodel.cpp
folderstatusdelegate.cpp
#include "ui_accountsettings.h"
#include "theme.h"
+#include "foldercreationdialog.h"
#include "folderman.h"
#include "folderwizard.h"
#include "folderstatusmodel.h"
openIgnoredFilesDialog(f->path());
}
+void AccountSettings::slotOpenMakeFolderDialog()
+{
+ const auto &selected = _ui->_folderList->selectionModel()->currentIndex();
+
+ if (!selected.isValid()) {
+ qCWarning(lcAccountSettings) << "Selection model current folder index is not valid.";
+ return;
+ }
+
+ const auto &classification = _model->classify(selected);
+
+ if (classification != FolderStatusModel::SubFolder && classification != FolderStatusModel::RootFolder) {
+ return;
+ }
+
+ const QString fileName = [this, &selected, &classification] {
+ QString result;
+ if (classification == FolderStatusModel::RootFolder) {
+ const auto alias = _model->data(selected, FolderStatusDelegate::FolderAliasRole).toString();
+ if (const auto folder = FolderMan::instance()->folder(alias)) {
+ result = folder->path();
+ }
+ } else {
+ result = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
+ }
+
+ if (result.endsWith('/')) {
+ result.chop(1);
+ }
+
+ return result;
+ }();
+
+ if (!fileName.isEmpty()) {
+ const auto folderCreationDialog = new FolderCreationDialog(fileName, this);
+ folderCreationDialog->setAttribute(Qt::WA_DeleteOnClose);
+ folderCreationDialog->open();
+ }
+}
+
void AccountSettings::slotEditCurrentLocalIgnoredFiles()
{
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
ac = menu.addAction(tr("Edit Ignored Files"));
connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentLocalIgnoredFiles);
+ ac = menu.addAction(tr("Create new folder"));
+ connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenMakeFolderDialog);
+ ac->setEnabled(QFile::exists(fileName));
+
const auto folder = info->_folder;
if (folder && folder->virtualFilesEnabled()) {
auto availabilityMenu = menu.addMenu(tr("Availability"));
ac = menu->addAction(tr("Edit Ignored Files"));
connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentIgnoredFiles);
+ ac = menu->addAction(tr("Create new folder"));
+ connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenMakeFolderDialog);
+ ac->setEnabled(QFile::exists(folder->path()));
+
if (!_ui->_folderList->isExpanded(index) && folder->supportsSelectiveSync()) {
ac = menu->addAction(tr("Choose what to sync"));
ac->setEnabled(folderConnected);
void slotOpenCurrentFolder(); // sync folder
void slotOpenCurrentLocalSubFolder(); // selected subfolder in sync folder
void slotEditCurrentIgnoredFiles();
+ void slotOpenMakeFolderDialog();
void slotEditCurrentLocalIgnoredFiles();
void slotEnableVfsCurrentFolder();
void slotDisableVfsCurrentFolder();
--- /dev/null
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
+ *
+ * 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.
+ */
+
+#include "foldercreationdialog.h"
+#include "ui_foldercreationdialog.h"
+
+#include <limits>
+
+#include <QDir>
+#include <QMessageBox>
+
+FolderCreationDialog::FolderCreationDialog(const QString &destination, QWidget *parent)
+ : QDialog(parent)
+ , ui(new Ui::FolderCreationDialog)
+ , _destination(destination)
+{
+ ui->setupUi(this);
+
+ ui->labelErrorMessage->setVisible(false);
+
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ connect(ui->newFolderNameEdit, &QLineEdit::textChanged, this, &FolderCreationDialog::slotNewFolderNameEditTextEdited);
+
+ const QString suggestedFolderNamePrefix = QObject::tr("New folder");
+
+ const auto newFolderFullPath = _destination + "/" + suggestedFolderNamePrefix;
+ if (!QDir(newFolderFullPath).exists()) {
+ ui->newFolderNameEdit->setText(suggestedFolderNamePrefix);
+ } else {
+ for (unsigned int i = 2; i < std::numeric_limits<unsigned int>::max(); ++i) {
+ const QString suggestedPostfix = QString(" (%1)").arg(i);
+
+ if (!QDir(newFolderFullPath + suggestedPostfix).exists()) {
+ ui->newFolderNameEdit->setText(suggestedFolderNamePrefix + suggestedPostfix);
+ break;
+ }
+ }
+ }
+
+ ui->newFolderNameEdit->setFocus();
+ ui->newFolderNameEdit->selectAll();
+}
+
+FolderCreationDialog::~FolderCreationDialog()
+{
+ delete ui;
+}
+
+void FolderCreationDialog::accept()
+{
+ Q_ASSERT(!_destination.endsWith('/'));
+
+ if (QDir(_destination + "/" + ui->newFolderNameEdit->text()).exists()) {
+ ui->labelErrorMessage->setVisible(true);
+ return;
+ }
+
+ if (!QDir(_destination).mkdir(ui->newFolderNameEdit->text())) {
+ QMessageBox::critical(this, tr("Error"), tr("Could not create a folder! Check your write permissions."));
+ }
+
+ QDialog::accept();
+}
+
+void FolderCreationDialog::slotNewFolderNameEditTextEdited()
+{
+ if (!ui->newFolderNameEdit->text().isEmpty() && QDir(_destination + "/" + ui->newFolderNameEdit->text()).exists()) {
+ ui->labelErrorMessage->setVisible(true);
+ } else {
+ ui->labelErrorMessage->setVisible(false);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
+ *
+ * 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.
+ */
+
+#ifndef FOLDERCREATIONDIALOG_H
+#define FOLDERCREATIONDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+class FolderCreationDialog;
+}
+
+class FolderCreationDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit FolderCreationDialog(const QString &destination, QWidget *parent = nullptr);
+ ~FolderCreationDialog();
+
+private slots:
+ void accept() override;
+
+ void slotNewFolderNameEditTextEdited();
+
+private:
+ Ui::FolderCreationDialog *ui;
+
+ QString _destination;
+};
+
+#endif // FOLDERCREATIONDIALOG_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FolderCreationDialog</class>
+ <widget class="QDialog" name="FolderCreationDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>355</width>
+ <height>138</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Create new folder</string>
+ </property>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>90</y>
+ <width>341</width>
+ <height>32</height>
+ </rect>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ <widget class="QLineEdit" name="newFolderNameEdit">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>30</y>
+ <width>321</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="placeholderText">
+ <string>Enter folder name</string>
+ </property>
+ </widget>
+ <widget class="QLabel" name="labelErrorMessage">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>60</y>
+ <width>321</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">color: rgb(255, 0, 0)</string>
+ </property>
+ <property name="text">
+ <string>Folder already exists</string>
+ </property>
+ </widget>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>FolderCreationDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>FolderCreationDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
return;
info->resetSubs(this, parent);
QString path = info->_folder->remotePathTrailingSlash();
- if (info->_path != QLatin1String("/")) {
- path += info->_path;
+
+ // info->_path always contains non-mangled name, so we need to use mangled when requesting nested folders for encrypted subfolders as required by LsColJob
+ const QString infoPath = (info->_isEncrypted && !info->_e2eMangledName.isEmpty()) ? info->_e2eMangledName : info->_path;
+
+ if (infoPath != QLatin1String("/")) {
+ path += infoPath;
}
auto *job = new LsColJob(_accountState->account(), path, this);
parentInfo->_folder->journalDb()->getFileRecordByE2eMangledName(removeTrailingSlash(relativePath), &rec);
if (rec.isValid()) {
newInfo._name = removeTrailingSlash(rec._path).split('/').last();
+ if (rec._isE2eEncrypted && !rec._e2eMangledName.isEmpty()) {
+ // we must use local path for Settings Dialog's filesystem tree, otherwise open and create new folder actions won't work
+ // hence, we are storing _e2eMangledName separately so it can be use later for LsColJob
+ newInfo._e2eMangledName = relativePath;
+ newInfo._path = rec._path;
+ }
+ if (!newInfo._path.endsWith('/')) {
+ newInfo._path += '/';
+ }
} else {
newInfo._name = removeTrailingSlash(relativePath).split('/').last();
}
struct SubFolderInfo
{
Folder *_folder = nullptr;
- QString _name;
- QString _path;
+ QString _name; // Folder name to be displayed in the UI
+ QString _path; // Sub-folder path that should always point to a local filesystem's folder
+ QString _e2eMangledName; // Mangled name that needs to be used when making fetch requests and should not be used for displaying in the UI
QVector<int> _pathIdx;
QVector<SubFolderInfo> _subs;
qint64 _size = 0;