diff --git a/Backend/FileStorageSubSystem/FileStorageManager.cpp b/Backend/FileStorageSubSystem/FileStorageManager.cpp index 5d80133..5482141 100644 --- a/Backend/FileStorageSubSystem/FileStorageManager.cpp +++ b/Backend/FileStorageSubSystem/FileStorageManager.cpp @@ -1,5 +1,6 @@ #include "FileStorageManager.h" +#include "Utility/AppConfig.h" #include "Utility/JsonDtoFormat.h" #include "Utility/DatabaseRegistry.h" @@ -11,7 +12,7 @@ FileStorageManager::FileStorageManager(const QSqlDatabase &db, const QString &backupFolderPath) { - setBackupFolderPath(backupFolderPath); + setStorageFolderPath(backupFolderPath); database = db; if(!database.isOpen()) @@ -24,14 +25,10 @@ FileStorageManager::FileStorageManager(const QSqlDatabase &db, const QString &ba QSharedPointer FileStorageManager::instance() { - QString tempPath = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::TempLocation); - tempPath = QDir::toNativeSeparators(tempPath) + QDir::separator(); - QString folderName = "backup_2"; - QDir(tempPath).mkdir(folderName); - + AppConfig config; QSqlDatabase storageDb = DatabaseRegistry::fileStorageDatabase(); - auto *rawPtr = new FileStorageManager(storageDb, tempPath + folderName); + auto *rawPtr = new FileStorageManager(storageDb, config.getStorageFolderPath()); auto result = QSharedPointer(rawPtr); return result; @@ -176,7 +173,7 @@ bool FileStorageManager::appendVersion(const QString &symbolFilePath, const QStr QFile file(pathToFile); QString internalFileName = generateRandomFileName(); - QString generatedFilePath = getBackupFolderPath() + internalFileName; + QString generatedFilePath = getStorageFolderPath() + internalFileName; bool isCopied = file.copy(generatedFilePath); if(!isCopied) @@ -238,7 +235,7 @@ bool FileStorageManager::deleteFile(const QString &symbolFilePath) QStringList internalPathList; for(const FileVersionEntity &version : fileVersionList) - internalPathList.append(getBackupFolderPath() + version.internalFileName); + internalPathList.append(getStorageFolderPath() + version.internalFileName); result = fileRepository->deleteEntity(entity); @@ -267,7 +264,7 @@ bool FileStorageManager::deleteFileVersion(const QString &symbolFilePath, qlongl if(result == true) { - QString internalFilePath = getBackupFolderPath() + entity.internalFileName; + QString internalFilePath = getStorageFolderPath() + entity.internalFileName; QFile::remove(internalFilePath); FileEntity parentEntity = fileRepository->findBySymbolPath(symbolFilePath, true); @@ -467,17 +464,17 @@ QJsonArray FileStorageManager::getActiveFileList() const return result; } -QString FileStorageManager::getBackupFolderPath() const +QString FileStorageManager::getStorageFolderPath() const { - return backupFolderPath; + return storageFolderPath; } -void FileStorageManager::setBackupFolderPath(const QString &newBackupFolderPath) +void FileStorageManager::setStorageFolderPath(const QString &newStorageFolderPath) { - backupFolderPath = QDir::toNativeSeparators(newBackupFolderPath); + storageFolderPath = QDir::toNativeSeparators(newStorageFolderPath); - if(!backupFolderPath.endsWith(QDir::separator())) - backupFolderPath.append(QDir::separator()); + if(!storageFolderPath.endsWith(QDir::separator())) + storageFolderPath.append(QDir::separator()); } QString FileStorageManager::generateRandomFileName() diff --git a/Backend/FileStorageSubSystem/FileStorageManager.h b/Backend/FileStorageSubSystem/FileStorageManager.h index 102f402..9df9f15 100644 --- a/Backend/FileStorageSubSystem/FileStorageManager.h +++ b/Backend/FileStorageSubSystem/FileStorageManager.h @@ -49,8 +49,8 @@ class FileStorageManager QJsonArray getActiveFolderList() const; QJsonArray getActiveFileList() const; - QString getBackupFolderPath() const; - void setBackupFolderPath(const QString &newBackupFolderPath); + QString getStorageFolderPath() const; + void setStorageFolderPath(const QString &newStorageFolderPath); private: QString generateRandomFileName(); @@ -60,7 +60,7 @@ class FileStorageManager bool sortFileVersionEntities(const FileEntity &parentEntity); private: - QString backupFolderPath; + QString storageFolderPath; QSqlDatabase database; FolderRepository *folderRepository; FileRepository *fileRepository; diff --git a/CMakeLists.txt b/CMakeLists.txt index 56e8f59..6774087 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,12 @@ set(PROJECT_SOURCES Gui/DataModels/DialogImport/ItemDelegateAction.h Gui/DataModels/DialogImport/ItemDelegateAction.cpp # + + # DialogSettings + Gui/Dialogs/DialogSettings.h + Gui/Dialogs/DialogSettings.cpp + Gui/Dialogs/DialogSettings.ui + # # TabFileExplorer Gui/Tabs/TabFileExplorer.h @@ -119,9 +125,12 @@ set(PROJECT_SOURCES # main.cpp + resources.qrc Utility/DatabaseRegistry.h Utility/DatabaseRegistry.cpp + Utility/AppConfig.h + Utility/AppConfig.cpp Utility/JsonDtoFormat.h Backend/FileStorageSubSystem/FileStorageManager.h diff --git a/Gui/Dialogs/DialogAddNewFolder.cpp b/Gui/Dialogs/DialogAddNewFolder.cpp index be4a1fc..8582017 100644 --- a/Gui/Dialogs/DialogAddNewFolder.cpp +++ b/Gui/Dialogs/DialogAddNewFolder.cpp @@ -76,7 +76,7 @@ void DialogAddNewFolder::on_buttonSelectFolder_clicked() auto fsm = FileStorageManager::instance(); QString selectedFolderPath = QDir::toNativeSeparators(dialog.selectedFiles().at(0)).append(QDir::separator()); - QStorageInfo storageInfo(fsm->getBackupFolderPath()); + QStorageInfo storageInfo(fsm->getStorageFolderPath()); qint64 folderSize = getFolderSize(selectedFolderPath); qint64 availableSize = storageInfo.bytesFree(); diff --git a/Gui/Dialogs/DialogCreateCopy.cpp b/Gui/Dialogs/DialogCreateCopy.cpp index d1a97eb..3c0264b 100644 --- a/Gui/Dialogs/DialogCreateCopy.cpp +++ b/Gui/Dialogs/DialogCreateCopy.cpp @@ -125,7 +125,7 @@ void DialogCreateCopy::on_buttonCreateCopy_clicked() auto fsm = FileStorageManager::instance(); QJsonObject fileJson = fsm->getFileVersionJson(currentFileSymbolPath, ui->comboBox->currentText().toInt()); - QString internalFilePath = fsm->getBackupFolderPath(); + QString internalFilePath = fsm->getStorageFolderPath(); internalFilePath += fileJson[JsonKeys::FileVersion::InternalFileName].toString(); QFile::remove(userFilePath); diff --git a/Gui/Dialogs/DialogExport.cpp b/Gui/Dialogs/DialogExport.cpp index 0915ab3..49cb43a 100644 --- a/Gui/Dialogs/DialogExport.cpp +++ b/Gui/Dialogs/DialogExport.cpp @@ -162,7 +162,7 @@ void DialogExport::on_buttonExport_clicked() { QJsonObject versionJson = currentFileVersion.toObject(); QString internalFileName = versionJson[JsonKeys::FileVersion::InternalFileName].toString(); - QString internalFilePath = fsm->getBackupFolderPath() + internalFileName; + QString internalFilePath = fsm->getStorageFolderPath() + internalFileName; QFile rawFile(internalFilePath); bool isReadable = rawFile.open(QFile::OpenModeFlag::ReadOnly); diff --git a/Gui/Dialogs/DialogSettings.cpp b/Gui/Dialogs/DialogSettings.cpp new file mode 100644 index 0000000..cadc999 --- /dev/null +++ b/Gui/Dialogs/DialogSettings.cpp @@ -0,0 +1,72 @@ +#include "DialogSettings.h" +#include "ui_DialogSettings.h" + +#include "Utility/AppConfig.h" + +#include +#include + +DialogSettings::DialogSettings(QWidget *parent) : + QDialog(parent), + ui(new Ui::DialogSettings) +{ + ui->setupUi(this); +} + +DialogSettings::~DialogSettings() +{ + delete ui; +} + +void DialogSettings::show() +{ + AppConfig config; + ui->lineEdit->setText(config.getStorageFolderPath()); + showStatusInfo(tr("No changes made made to settings yet."), ui->labelStatus); + ui->buttonSave->setDisabled(true); + + QWidget::show(); +} + +void DialogSettings::on_buttonSelectStorageFolder_clicked() +{ + QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::DesktopLocation); + desktopPath = QDir::toNativeSeparators(desktopPath); + desktopPath += QDir::separator(); + + QString folderPath = QFileDialog::getExistingDirectory(this, tr("Select a profile folder"), desktopPath); + + if(folderPath.isEmpty()) + return; + + folderPath = QDir::toNativeSeparators(folderPath) + QDir::separator(); + ui->lineEdit->setText(folderPath); + + bool isExist = QFile::exists(folderPath + "ns_database.db3"); + + if(isExist) + { + QString message = tr("Existing profile found at selected location.
" + "After saving, selected profile will be loaded. NOTE: Saving requires restard."); + + showStatusSuccess(message, ui->labelStatus); + } + else + { + QString message = tr("No profile found at selected location. New profile will be created.
" + "After saving, new and empty profile will be loaded. NOTE: Saving requires restard."); + + showStatusWarning(message, ui->labelStatus); + } + + ui->buttonSave->setEnabled(true); +} + + +void DialogSettings::on_buttonSave_clicked() +{ + AppConfig config; + config.setStorageFolderPath(ui->lineEdit->text()); + qApp->exit(0); +} + diff --git a/Gui/Dialogs/DialogSettings.h b/Gui/Dialogs/DialogSettings.h new file mode 100644 index 0000000..deb1086 --- /dev/null +++ b/Gui/Dialogs/DialogSettings.h @@ -0,0 +1,31 @@ +#ifndef DIALOGSETTINGS_H +#define DIALOGSETTINGS_H + +#include "BaseDialog.h" + +#include + +namespace Ui { +class DialogSettings; +} + +class DialogSettings : public QDialog, public BaseDialog +{ + Q_OBJECT + +public: + explicit DialogSettings(QWidget *parent = nullptr); + ~DialogSettings(); + + void show(); + +private slots: + void on_buttonSelectStorageFolder_clicked(); + + void on_buttonSave_clicked(); + +private: + Ui::DialogSettings *ui; +}; + +#endif // DIALOGSETTINGS_H diff --git a/Gui/Dialogs/DialogSettings.ui b/Gui/Dialogs/DialogSettings.ui new file mode 100644 index 0000000..7d087a8 --- /dev/null +++ b/Gui/Dialogs/DialogSettings.ui @@ -0,0 +1,141 @@ + + + DialogSettings + + + + 0 + 0 + 640 + 161 + + + + + 0 + 0 + + + + + 0 + 161 + + + + + 16777215 + 161 + + + + Setting Editor + + + + + + Storage + + + + + + + 0 + 30 + + + + + true + + + + true + + + + + + + + 150 + 30 + + + + Select Storage Folder + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 150 + 30 + + + + Save + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + QLabel + { +padding: 5px 5px 5px 0px; +} + + + Status Here + + + + + + + + diff --git a/Gui/MainWindow.cpp b/Gui/MainWindow.cpp index 8ed3005..e3e622a 100644 --- a/Gui/MainWindow.cpp +++ b/Gui/MainWindow.cpp @@ -1,11 +1,14 @@ #include "MainWindow.h" #include "ui_MainWindow.h" +#include "Utility/AppConfig.h" #include "Utility/JsonDtoFormat.h" #include "Backend/FileStorageSubSystem/FileStorageManager.h" #include #include +#include +#include #include #include @@ -17,12 +20,13 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); QThread::currentThread()->setObjectName(guiThreadName()); + dialogSettings = new DialogSettings(this); dialogImport = new DialogImport(this); dialogAddNewFolder = new DialogAddNewFolder(this); dialogDebugFileMonitor = new DialogDebugFileMonitor(this); - allocateSeparators(); buildTabWidget(); + createTrayIcon(); disableCloseButtonOfPredefinedTabs(); on_tabWidget_currentChanged(ui->tabWidget->currentIndex()); @@ -40,12 +44,13 @@ MainWindow::MainWindow(QWidget *parent) : QObject::connect(tabFileExplorer, &TabFileExplorer::signalRefreshFileMonitor, tabFileMonitor, &TabFileMonitor::onEventDbUpdated); + QObject::connect(tabFileMonitor, &TabFileMonitor::signalFileMonitorRefreshed, + this, &MainWindow::onNotificationRequested); + QObject::connect(tabFileMonitor, &TabFileMonitor::signalEnableSaveAllButton, ui->tab2Action_SaveAll, &QAction::setEnabled); createFileMonitorThread(dialogImport, tabFileExplorer); - - showLiabilityWarningInStatusBar(); } MainWindow::~MainWindow() @@ -61,27 +66,76 @@ QString MainWindow::guiThreadName() const return "GUI Thread"; } +void MainWindow::closeEvent(QCloseEvent *event) +{ + if (!event->spontaneous() || !isVisible()) + return; + + AppConfig config; + + if (trayIcon->isVisible() && !config.isTrayIconInformed()) + { + config.setTrayIconInformed(true); + + QMessageBox::information(this, tr("Running in the background"), + tr("NeSync windows will be minimized to system tray.
" + "However, your folders still be monitored in the background.
" + "To terminate the NeSync, choose Quit in the context menu of the system tray entry.")); + hide(); + event->ignore(); + } +} + +void MainWindow::onTrayIconClicked(QSystemTrayIcon::ActivationReason reason) +{ + if(reason == QSystemTrayIcon::ActivationReason::Context) + return; + + show(); +} + +void MainWindow::onNotificationRequested() +{ + if(isVisible()) + qApp->beep(); + else + { + QString title = tr("Activity detected on your folders !"); + QString message = tr("Click here to display file monitor."); + + trayIcon->showMessage(title, message); + return; + } +} + +void MainWindow::onTrayIconMessageClicked() +{ + ui->tabWidget->setCurrentIndex(TabIndexMonitor); + show(); +} + void MainWindow::on_tabWidget_currentChanged(int index) { QToolBar *toolBar = ui->toolBar; + + for(const QAction *action : toolBar->actions()) + { + if(action->isSeparator()) + delete action; + } + toolBar->clear(); - if(index == 1) + if(index == TabIndexMonitor) toolBar->addAction(ui->tab2Action_SaveAll); - else if(index == 0) + else if(index == TabIndexExplorer) { toolBar->addAction(ui->tab1Action_AddNewFolder); - toolBar->addAction(separator1); + toolBar->addSeparator(); toolBar->addAction(ui->tab1Action_Import); } } -void MainWindow::allocateSeparators() -{ - separator1 = new QAction(this); - separator1->setSeparator(true); -} - void MainWindow::buildTabWidget() { tabFileExplorer = new TabFileExplorer(ui->tabWidget); @@ -94,11 +148,36 @@ void MainWindow::buildTabWidget() void MainWindow::disableCloseButtonOfPredefinedTabs() { QTabBar *tabBar = ui->tabWidget->tabBar(); - tabBar->tabButton(0, QTabBar::ButtonPosition::RightSide)->deleteLater(); - tabBar->setTabButton(0, QTabBar::ButtonPosition::RightSide, nullptr); + tabBar->tabButton(TabIndexExplorer, QTabBar::ButtonPosition::RightSide)->deleteLater(); + tabBar->setTabButton(TabIndexExplorer, QTabBar::ButtonPosition::RightSide, nullptr); + + tabBar->tabButton(TabIndexMonitor, QTabBar::ButtonPosition::RightSide)->deleteLater(); + tabBar->setTabButton(TabIndexMonitor, QTabBar::ButtonPosition::RightSide, nullptr); +} + +void MainWindow::createTrayIcon() +{ + QAction *showAction = new QAction(tr("&Show"), this); + QObject::connect(showAction, &QAction::triggered, this, &QWidget::showNormal); + + QAction *quitAction = new QAction(tr("&Quit"), this); + QObject::connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit); + + trayIconMenu = new QMenu(this); + trayIconMenu->addAction(showAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(quitAction); + + trayIcon = new QSystemTrayIcon(this); + QObject::connect(trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::onTrayIconClicked); + QObject::connect(trayIcon, &QSystemTrayIcon::messageClicked, this, &MainWindow::onTrayIconMessageClicked); + trayIcon->setContextMenu(trayIconMenu); + + QIcon icon(":/Resources/test_icon.png"); + trayIcon->setIcon(icon); - tabBar->tabButton(1, QTabBar::ButtonPosition::RightSide)->deleteLater(); - tabBar->setTabButton(1, QTabBar::ButtonPosition::RightSide, nullptr); + trayIcon->show(); + trayIcon->setToolTip("NeSync"); } void MainWindow::createFileMonitorThread(const DialogImport * const dialogImport, @@ -185,6 +264,12 @@ void MainWindow::on_tab2Action_SaveAll_triggered() tabFileMonitor->saveChanges(fmm); } +void MainWindow::on_menuAction_Settings_triggered() +{ + dialogSettings->setModal(true); + dialogSettings->show(); +} + void MainWindow::on_menuAction_DebugFileMonitor_triggered() { dialogDebugFileMonitor->show(); @@ -192,10 +277,8 @@ void MainWindow::on_menuAction_DebugFileMonitor_triggered() void MainWindow::on_menuAction_AboutApp_triggered() { - auto fsm = FileStorageManager::instance(); - QString title = tr("About NeSync"); - QString message = tr("

NeSync 1.5.0 [Pre-Alpha]

" + QString message = tr("

NeSync 1.6.0 [Pre-Alpha]

" "
" "Thanks for using NeSync.
" "This is a pre-alpha version, DO NOT USE for critical things.
" @@ -241,8 +324,7 @@ void MainWindow::on_menuAction_AboutApp_triggered() "
" "Source code is available in this GitHub Repo." "
" - "
" - "Backup folder path: %1").arg(fsm->getBackupFolderPath()); + ); QMessageBox::information(this, title, message); } @@ -251,17 +333,3 @@ void MainWindow::on_menuAction_AboutQt_triggered() { QApplication::aboutQt(); } - -void MainWindow::showLiabilityWarningInStatusBar() -{ - QPalette palette(QPalette::ColorRole::WindowText, "#e84118"); - - QLabel *label = new QLabel(this); - - label->setStyleSheet("QLabel { padding: 5px 5px 5px 0px; }"); - label->setPalette(palette); - label->setAutoFillBackground(true); - label->setText(tr("This is a pre-alpha version which means USE AT YOUR OWN RISK. --- [Version 1.5.0]")); - - ui->statusbar->insertWidget(0, label); -} diff --git a/Gui/MainWindow.h b/Gui/MainWindow.h index 359af3f..c8927eb 100644 --- a/Gui/MainWindow.h +++ b/Gui/MainWindow.h @@ -4,12 +4,14 @@ #include "Tabs/TabFileMonitor.h" #include "Tabs/TabFileExplorer.h" #include "Dialogs/DialogImport.h" +#include "Dialogs/DialogSettings.h" #include "Dialogs/DialogAddNewFolder.h" #include "Dialogs/DialogDebugFileMonitor.h" #include "Backend/FileMonitorSubSystem/FileMonitoringManager.h" #include #include +#include namespace Ui { @@ -21,15 +23,25 @@ class MainWindow : public QMainWindow Q_OBJECT public: + static const inline int TabIndexExplorer = 0; + static const inline int TabIndexMonitor = 1; + explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); QString guiThreadName() const; +protected: + void closeEvent(QCloseEvent *event) override; + private slots: + void onTrayIconClicked(QSystemTrayIcon::ActivationReason reason); + void onNotificationRequested(); + void onTrayIconMessageClicked(); void on_tabWidget_currentChanged(int index); void on_tab1Action_AddNewFolder_triggered(); void on_tab1Action_Import_triggered(); void on_tab2Action_SaveAll_triggered(); + void on_menuAction_Settings_triggered(); void on_menuAction_DebugFileMonitor_triggered(); void on_menuAction_AboutApp_triggered(); void on_menuAction_AboutQt_triggered(); @@ -37,19 +49,21 @@ private slots: private: TabFileExplorer *tabFileExplorer; TabFileMonitor *tabFileMonitor; - QAction *separator1; private: - void showLiabilityWarningInStatusBar(); - void allocateSeparators(); void buildTabWidget(); void disableCloseButtonOfPredefinedTabs(); + void createTrayIcon(); void createFileMonitorThread(const DialogImport * const dialogImport, const TabFileExplorer * const tabFileExplorer); QString fileMonitorThreadName() const; private: Ui::MainWindow *ui; + QSystemTrayIcon *trayIcon; + QMenu *trayIconMenu; + + DialogSettings *dialogSettings; DialogAddNewFolder *dialogAddNewFolder; DialogImport *dialogImport; DialogDebugFileMonitor *dialogDebugFileMonitor; diff --git a/Gui/MainWindow.ui b/Gui/MainWindow.ui index ce91a09..98e12e9 100644 --- a/Gui/MainWindow.ui +++ b/Gui/MainWindow.ui @@ -57,6 +57,12 @@ 21 + + + Manage + + + Debug @@ -70,48 +76,46 @@ + - + - Import + Add New Folder + + + Add New Folder - + - About App + Import - + - About Qt + Save All - + - Refresh - - - Refresh + Settings - + - Save All + Debug File Monitor - + - Add New Folder - - - Add New Folder + About App - + - Debug File Monitor + About Qt diff --git a/Gui/Tabs/TabFileExplorer.cpp b/Gui/Tabs/TabFileExplorer.cpp index 347a6eb..cec3f1f 100644 --- a/Gui/Tabs/TabFileExplorer.cpp +++ b/Gui/Tabs/TabFileExplorer.cpp @@ -400,7 +400,7 @@ void TabFileExplorer::on_contextActionListView_DeleteVersion_triggered() { fileJson = fsm->getFileJsonBySymbolPath(symbolFilePath); QJsonObject versionJson = fsm->getFileVersionJson(symbolFilePath, fileJson[JsonKeys::File::MaxVersionNumber].toInteger()); - QString internalFilePath = fsm->getBackupFolderPath() + versionJson[JsonKeys::FileVersion::InternalFileName].toString(); + QString internalFilePath = fsm->getStorageFolderPath() + versionJson[JsonKeys::FileVersion::InternalFileName].toString(); QFile::remove(userFilePath); QFile::copy(internalFilePath, userFilePath); @@ -471,7 +471,7 @@ void TabFileExplorer::on_contextActionListView_SetAsCurrentVersion_triggered() fsm->updateFileVersionEntity(versionJson); fsm->sortFileVersionsInIncreasingOrder(symbolFilePath); - QString internalFilePath = fsm->getBackupFolderPath(); + QString internalFilePath = fsm->getStorageFolderPath(); internalFilePath += versionJson[JsonKeys::FileVersion::InternalFileName].toString(); QFile::copy(internalFilePath, userFilePath); @@ -690,16 +690,21 @@ void TabFileExplorer::executeFreezingOrThawingOfFile(const QString &name, const QJsonObject versionJson = fsm->getFileVersionJson(symbolPath, fileJson[JsonKeys::File::MaxVersionNumber].toInteger()); QString userFolderPath = parentFolderJson[JsonKeys::Folder::UserFolderPath].toString(); QString userFilePath = userFolderPath + name; - QString internalFilePath = fsm->getBackupFolderPath() + versionJson[JsonKeys::FileVersion::InternalFileName].toString(); + QString internalFilePath = fsm->getStorageFolderPath() + versionJson[JsonKeys::FileVersion::InternalFileName].toString(); bool isExist = QFile::exists(userFilePath); if(isExist) { QString title = tr("File exist at location !"); - QString message = tr("Can't overwrite file %1 in location you selected."); - message = message.arg(name); - QMessageBox::critical(this, title, message); - return; + QString message = tr("Thawing %1 will overwrite already existing file at:
" + "
%2

" + "Would you like the replace the file ?"); + message = message.arg(name, userFilePath); + + QMessageBox::StandardButton result = QMessageBox::question(this, title, message); + + if(result != QMessageBox::StandardButton::Yes) + return; } emit signalStopFileMonitor(); @@ -715,6 +720,9 @@ void TabFileExplorer::executeFreezingOrThawingOfFile(const QString &name, const bool isCopied = false; QFuture future = QtConcurrent::run([=, &isCopied] { + if(isExist) + QFile::remove(userFilePath); + isCopied = QFile::copy(internalFilePath, userFilePath); }); @@ -783,7 +791,7 @@ void TabFileExplorer::thawFolderTree(const QString folderName, const QString &pa QJsonObject versionJson = fsm->getFileVersionJson(fileJson[JsonKeys::File::SymbolFilePath].toString(), fileJson[JsonKeys::File::MaxVersionNumber].toInteger()); - QString internalFilePath = fsm->getBackupFolderPath(); + QString internalFilePath = fsm->getStorageFolderPath(); internalFilePath.append(versionJson[JsonKeys::FileVersion::InternalFileName].toString()); QString userFilePath = currentUserPath + fileJson[JsonKeys::File::FileName].toString(); diff --git a/Gui/Tabs/TabFileMonitor.cpp b/Gui/Tabs/TabFileMonitor.cpp index 7466802..aa8507e 100644 --- a/Gui/Tabs/TabFileMonitor.cpp +++ b/Gui/Tabs/TabFileMonitor.cpp @@ -95,7 +95,10 @@ void TabFileMonitor::displayFileMonitorContent() FileSystemEventDb fsEventDb(DatabaseRegistry::fileSystemEventDatabase()); if(fsEventDb.isContainAnyFolderEvent() || fsEventDb.isContainAnyFileEvent()) + { emit signalEnableSaveAllButton(true); + emit signalFileMonitorRefreshed(); + } ui->labelStatus->setHidden(true); ui->progressBar->hide(); diff --git a/Gui/Tabs/TabFileMonitor.h b/Gui/Tabs/TabFileMonitor.h index 95924fd..29ea810 100644 --- a/Gui/Tabs/TabFileMonitor.h +++ b/Gui/Tabs/TabFileMonitor.h @@ -27,6 +27,7 @@ public slots: signals: void signalEnableSaveAllButton(bool flag); + void signalFileMonitorRefreshed(); private slots: void displayFileMonitorContent(); diff --git a/Gui/Tasks/TaskSaveChanges.cpp b/Gui/Tasks/TaskSaveChanges.cpp index 053b158..05386a7 100644 --- a/Gui/Tasks/TaskSaveChanges.cpp +++ b/Gui/Tasks/TaskSaveChanges.cpp @@ -161,7 +161,7 @@ void TaskSaveChanges::saveFileChanges() qlonglong maxVersionNumber = fileJson[JsonKeys::File::MaxVersionNumber].toInteger(); QJsonObject versionJson = fsm->getFileVersionJson(symbolFilePath, maxVersionNumber); QString internalFileName = versionJson[JsonKeys::FileVersion::InternalFileName].toString(); - auto internalFilePath = fsm->getBackupFolderPath() + internalFileName; + auto internalFilePath = fsm->getStorageFolderPath() + internalFileName; QString userFilePath = fileJson[JsonKeys::File::UserFilePath].toString(); QFile::remove(item->getUserPath()); // If restored file exist remove it @@ -181,7 +181,7 @@ void TaskSaveChanges::saveFileChanges() qlonglong maxVersionNumber = fileJson[JsonKeys::File::MaxVersionNumber].toInteger(); QJsonObject versionJson = fsm->getFileVersionJson(symbolFilePath, maxVersionNumber); QString internalFileName = versionJson[JsonKeys::FileVersion::InternalFileName].toString(); - auto internalFilePath = fsm->getBackupFolderPath() + internalFileName; + auto internalFilePath = fsm->getStorageFolderPath() + internalFileName; QString userFilePath = fileJson[JsonKeys::File::UserFilePath].toString(); QFile::remove(item->getUserPath()); diff --git a/README.md b/README.md index da81728..ffc5ba7 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,15 @@ __WARNING: NeSync is currently in pre-alpha stage and it's not complete yet.__ Executables are available in the [Releases](https://github.com/fxdeniz/NeSync/releases) page. +## Screenshots +### Folder Monitor +![monitor](https://github.com/fxdeniz/NeSync/assets/104620840/022d9798-bc30-4ace-8796-0f71d70f4936) +### File Explorer +![mainwindow](https://github.com/fxdeniz/NeSync/assets/104620840/3780549f-3a4b-474b-8696-a0214b7dfb4b) +### Backup Import Screen +![import](https://github.com/fxdeniz/NeSync/assets/104620840/06664327-5bb1-41ac-ac1c-6fe1a8526d38) + + ## Features * Fully multi-threaded architecture * Small memory footprint @@ -36,17 +45,15 @@ Executables are available in the [Releases](https://github.com/fxdeniz/NeSync/re ## Few important notes -1. Your files are stored in folder called __backup_2__ in your temp directory. - -2. You will see different project names in the git history such as `XdBackup` and `BoldBackup`.
+1. You will see different project names in the git history such as `XdBackup` and `BoldBackup`.
These are old names for the __NeSync__. -3. If you detect network activity related with __NeSync__ process you can safely ignore it.
+2. If you detect network activity related with __NeSync__ process you can safely ignore it.
This activities probably generated by the OS. Especially on Windows (see VirusTotal link in download section). -4. MacOS support is not properly tested and I __DO NOT provide__ official builds currently. +3. MacOS support is not properly tested and I __DO NOT provide__ official builds currently. -5. When importing a zip file, json schema of `import.json` __IS NOT verified__.
+4. When importing a zip file, json schema of `import.json` __IS NOT verified__.
This is a huge security risk. Only import files which you know the source. @@ -124,9 +131,8 @@ Example: ## Future plans For the near future I'm planning to add these features: -1. Settings dialog -2. Documentation for the code (compatible with Doxygen) -3. Local logging +1. Documentation for the code (compatible with Doxygen) +2. Local logging ## Thanks to diff --git a/Resources/test_icon.png b/Resources/test_icon.png new file mode 100644 index 0000000..83ec193 Binary files /dev/null and b/Resources/test_icon.png differ diff --git a/Utility/AppConfig.cpp b/Utility/AppConfig.cpp new file mode 100644 index 0000000..cafa569 --- /dev/null +++ b/Utility/AppConfig.cpp @@ -0,0 +1,96 @@ +#include "AppConfig.h" + +#include +#include +#include +#include + +QReadWriteLock AppConfig::lock; + +AppConfig::AppConfig() +{ + QString settingsFilePath = QDir::toNativeSeparators(QCoreApplication::applicationDirPath()); + settingsFilePath += QDir::separator(); + settingsFilePath += "settings.ini"; + + settings = new QSettings(settingsFilePath, QSettings::Format::IniFormat); +} + +AppConfig::~AppConfig() +{ + delete settings; +} + +bool AppConfig::isDisclaimerAccepted() const +{ + QReadLocker readLocker(&lock); + + if(settings->value(KeyDisclaimerAccepted).toString() == "true") + return true; + + return false; +} + +void AppConfig::setDisclaimerAccepted(bool newDisclaimerAccepted) +{ + QWriteLocker writeLocker(&lock); + + settings->setValue(KeyDisclaimerAccepted, newDisclaimerAccepted); +} + +bool AppConfig::isTrayIconInformed() const +{ + QReadLocker readLocker(&lock); + + if(settings->value(KeyTrayIconInformed).toString() == "true") + return true; + + return false; +} + +void AppConfig::setTrayIconInformed(bool newTrayIconInformed) +{ + QWriteLocker writeLocker(&lock); + + settings->setValue(KeyTrayIconInformed, newTrayIconInformed); +} + +bool AppConfig::isStorageFolderPathValid() const +{ + QReadLocker readLocker(&lock); + + QString readValue = settings->value(KeyStorageFolderPath).toString(); + + if(readValue.isNull() || readValue.isEmpty()) + return false; + + if(!QDir(readValue).exists()) + return false; + + return true; +} + +QString AppConfig::getStorageFolderPath() const +{ + QReadLocker readLocker(&lock); + + QString readValue = settings->value(KeyStorageFolderPath).toString(); + readValue = QDir::toNativeSeparators(readValue); + + if(!readValue.endsWith(QDir::separator())) + readValue.append(QDir::separator()); + + return readValue; +} + +void AppConfig::setStorageFolderPath(const QString &newStorageFolderPath) +{ + QWriteLocker writeLocker(&lock); + + QString value = QDir::toNativeSeparators(newStorageFolderPath); + + if(!value.endsWith(QDir::separator())) + value.append(QDir::separator()); + + settings->setValue(KeyStorageFolderPath, value); +} diff --git a/Utility/AppConfig.h b/Utility/AppConfig.h new file mode 100644 index 0000000..a379857 --- /dev/null +++ b/Utility/AppConfig.h @@ -0,0 +1,34 @@ +#ifndef APPCONFIG_H +#define APPCONFIG_H + +#include +#include + +class AppConfig +{ +public: + AppConfig(); + ~AppConfig(); + + bool isDisclaimerAccepted() const; + void setDisclaimerAccepted(bool newDisclaimerAccepted); + + bool isTrayIconInformed() const; + void setTrayIconInformed(bool newTrayIconInformed); + + bool isStorageFolderPathValid() const; + QString getStorageFolderPath() const; + void setStorageFolderPath(const QString &newStorageFolderPath); + +private: + static const inline QString KeyDisclaimerAccepted = "disclaimer_accepted"; + static const inline QString KeyTrayIconInformed = "tray_icon_informed"; + static const inline QString KeyStorageFolderPath = "storage_folder_path"; + + static QReadWriteLock lock; + +private: + QSettings *settings; +}; + +#endif // APPCONFIG_H diff --git a/Utility/DatabaseRegistry.cpp b/Utility/DatabaseRegistry.cpp index 736ee0d..2fb9079 100644 --- a/Utility/DatabaseRegistry.cpp +++ b/Utility/DatabaseRegistry.cpp @@ -1,11 +1,11 @@ #include "DatabaseRegistry.h" +#include "Utility/AppConfig.h" + #include #include #include #include -#include -#include QSqlDatabase DatabaseRegistry::dbFileStorage; QSqlDatabase DatabaseRegistry::dbFileMonitor; @@ -49,20 +49,14 @@ QSqlDatabase DatabaseRegistry::fileSystemEventDatabase() void DatabaseRegistry::createDbFileStorage() { - QString dbPath = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::TempLocation); - dbPath += QDir::separator(); - dbPath += "backup_2"; - dbPath += QDir::separator(); + AppConfig config; + + QString dbPath = config.getStorageFolderPath(); QDir().mkdir(dbPath); dbPath += "ns_database.db3"; -// dbPath += QUuid::createUuid().toString(QUuid::StringFormat::Id128); -// dbPath += ".db3"; - - dbPath = QDir::toNativeSeparators(dbPath); - bool isExist = QFile(dbPath).exists(); dbFileStorage = QSqlDatabase::addDatabase("QSQLITE", "file_storage_db"); @@ -123,13 +117,6 @@ void DatabaseRegistry::createDbFileStorage() void DatabaseRegistry::createDbFileMonitor() { - QRandomGenerator *generator = QRandomGenerator::system(); - //QRandomGenerator *generator = new QRandomGenerator(); - QString dbPath = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::DesktopLocation); - dbPath += QDir::separator(); - dbPath += QString::number(generator->generate()) + ".db3"; - dbPath = QDir::toNativeSeparators(dbPath); - dbFileMonitor = QSqlDatabase::addDatabase("QSQLITE", "file_system_event_db"); dbFileMonitor.setConnectOptions("QSQLITE_OPEN_URI;QSQLITE_ENABLE_SHARED_CACHE"); @@ -138,8 +125,6 @@ void DatabaseRegistry::createDbFileMonitor() connectionString = connectionString.arg(randomDbFileName); dbFileMonitor.setDatabaseName(connectionString); - //dbFileMonitor.setDatabaseName(dbPath); - //database.setDatabaseName(":memory:"); dbFileMonitor.open(); dbFileMonitor.exec("PRAGMA foreign_keys = ON;"); diff --git a/main.cpp b/main.cpp index fa70931..493e3fc 100644 --- a/main.cpp +++ b/main.cpp @@ -1,41 +1,81 @@ -#include -#include #include +#include +#include +#include #include "Gui/MainWindow.h" +#include "Utility/AppConfig.h" + +bool askAcceptenceForDisclaimer(); +void showStorageLocationMessage(); int main(int argc, char *argv[]) { QApplication app(argc, argv); - QString settingsFilePath = QDir::toNativeSeparators(app.applicationDirPath()) + QDir::separator() + "settings.ini"; - QSettings settings(settingsFilePath, QSettings::Format::IniFormat); - QString settingKey = "disclaimer_accepted"; + AppConfig config; - if(settings.value(settingKey).toString() != "true") // Value can't read or not accepted + if(!config.isDisclaimerAccepted()) { - QString title = QObject::tr("Disclaimer !"); - QString message = QObject::tr("You're about the use Pre-Alpha version of NeSync." - "

" - "Pre-Alpha version means, this software IS NOT complete yet and contains bugs." - "
" - "Developer of this software DOES NOT recommed you to use this software for critical things." - "

" - "By pressing Yes you accept all the risks associated with using in-complete software." - " Such as data loss, system crashes and security breaches." - "

" - "Do you accept the accept the risks ?"); - - QMessageBox::StandardButton result = QMessageBox::question(nullptr, title, message); - - if(result != QMessageBox::StandardButton::Yes) + bool isAccepted = askAcceptenceForDisclaimer(); + + if(isAccepted) + config.setDisclaimerAccepted(true); + else return 0; } - settings.setValue(settingKey, true); + if(!config.isStorageFolderPathValid()) + showStorageLocationMessage(); + + QApplication::setQuitOnLastWindowClosed(false); MainWindow w; w.show(); return app.exec(); } + +bool askAcceptenceForDisclaimer() +{ + QString title = QObject::tr("Disclaimer !"); + QString message = QObject::tr("You're about the use Pre-Alpha version of NeSync." + "

" + "Pre-Alpha version means, this software IS NOT complete yet and contains bugs." + "
" + "Developer of this software DOES NOT recommed you to use this software for critical things." + "

" + "By pressing Yes you accept all the risks associated with using in-complete software." + " Such as data loss, system crashes and security breaches." + "

" + "Do you accept the accept the risks ?"); + + QMessageBox::StandardButton result = QMessageBox::question(nullptr, title, message); + + if(result == QMessageBox::StandardButton::Yes) + return true; + else + return false; +} + +void showStorageLocationMessage() +{ + QString storagePath = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::HomeLocation); + storagePath = QDir::toNativeSeparators(storagePath) + QDir::separator(); + storagePath += "nesync_"; + storagePath += QUuid::createUuid().toString(QUuid::StringFormat::WithoutBraces).mid(0, 8); + storagePath += QDir::separator(); + + QDir().mkpath(storagePath); + AppConfig().setStorageFolderPath(storagePath); + + QString title = QObject::tr("Auto generated storage folder !"); + + QString message = QObject::tr("NeSync uses file database which stores your files behind the scenes.
" + "This may require a lot of storage space (depending on size of your files).
" + "Auto generated storage folder is at:
" + "
%1

" + "If you want, you can change storage folder location from settings.").arg(storagePath); + + QMessageBox::information(nullptr, title, message); +} diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..30b0796 --- /dev/null +++ b/resources.qrc @@ -0,0 +1,5 @@ + + + Resources/test_icon.png + +