Prechádzať zdrojové kódy

modified: Move dupfind_cache.db and inifile to OS default directory.

Satoshi Yoneda 3 týždňov pred
rodič
commit
d25332be0e
3 zmenil súbory, kde vykonal 157 pridanie a 48 odobranie
  1. 44 0
      CMakeLists.txt
  2. 46 6
      src/CmdMain.cpp
  3. 67 42
      src/MainWindow.cpp

+ 44 - 0
CMakeLists.txt

@@ -65,3 +65,47 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
 add_executable(DupFindCmd src/CmdMain.cpp)
 target_link_libraries(DupFindCmd PRIVATE DupFindCore)
 
+# -----------------------------------------------------------------------------
+# Deployment Configuration (Windows)
+# -----------------------------------------------------------------------------
+if(WIN32)
+    # UCRTライブラリを自動的に含める
+    set(CMAKE_INSTALL_UCRT_LIBRARIES TRUE)
+    include(InstallRequiredSystemLibraries)
+    # vc_redist.x64.exeを外す
+    if(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS)
+        list(FILTER CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS EXCLUDE REGEX "\\.exe$")
+    endif()
+    
+    # Qt6::qmake 経由で bin ディレクトリを特定し windeployqt を探す
+    get_target_property(_qmake_exe Qt6::qmake IMPORTED_LOCATION)
+    get_filename_component(_qt_bin_dir "${_qmake_exe}" DIRECTORY)
+    find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}")
+
+    set(DEPLOY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Deploy/DupFind")
+
+    # `cmake --build build --target deploy` で起動する専用ターゲット
+    add_custom_target(deploy
+        # 1. 展開用ディレクトリの作成
+        COMMAND ${CMAKE_COMMAND} -E make_directory "${DEPLOY_DIR}"
+        
+        # 2. 実行ファイル本体のコピー
+        COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${PROJECT_NAME}>" "${DEPLOY_DIR}/"
+        COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:DupFindCmd>" "${DEPLOY_DIR}/"
+        
+        # 3. リソースディレクトリのコピー
+        COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/resources" "${DEPLOY_DIR}/resources"
+        
+        # 4. vcpkgがビルドディレクトリに出力した サードパーティDLL (OpenCV, SQLiteなど) の収集
+        COMMAND powershell -NoProfile -Command "Copy-Item -Path '$<TARGET_FILE_DIR:${PROJECT_NAME}>/*.dll' -Destination '${DEPLOY_DIR}/' -Force -ErrorAction SilentlyContinue"
+
+        # UCRTライブラリのコピー
+        COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS} "${DEPLOY_DIR}/"
+        
+        # 5. windeployqt による Qt DLL (--compiler-runtime) の自動収集
+        COMMAND "${WINDEPLOYQT_EXECUTABLE}" --no-translations --dir "${DEPLOY_DIR}" "${DEPLOY_DIR}/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
+        
+        DEPENDS ${PROJECT_NAME} DupFindCmd
+        COMMENT "Deploying and bundling all dependencies into ${DEPLOY_DIR}..."
+    )
+endif()

+ 46 - 6
src/CmdMain.cpp

@@ -1,8 +1,11 @@
 #include <QCoreApplication>
+#include <QDir>
 #include <QDirIterator>
 #include <QProcess>
 #include <QSettings>
 #include <QSharedMemory>
+#include <QStandardPaths>
+#include <QString>
 #include <QStringList>
 #include <QThread>
 #include <chrono>
@@ -24,6 +27,9 @@ int main(int argc, char *argv[]) {
   // OpenCVの不要なINFOログ(並列バックエンド読み込み失敗など)を抑制する
   cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);
 
+  QCoreApplication::setOrganizationName("Yoneda");
+  QCoreApplication::setApplicationName("DupFind");
+
   QCoreApplication app(argc, argv);
   QStringList args = app.arguments();
 
@@ -83,7 +89,12 @@ int main(int argc, char *argv[]) {
 }
 
 void cmdTh(int thValue) {
-  QString iniPath = QCoreApplication::applicationDirPath() + "/DupFind.ini";
+  QString confPath =
+      QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
+  if (!QDir().exists(confPath)) {
+    QDir().mkpath(confPath);
+  }
+  QString iniPath = confPath + "/settings.ini";
   QSettings settings(iniPath, QSettings::IniFormat);
   settings.setValue("threshold", thValue);
   std::cout << "Successfully updated threshold to " << thValue << ".\n";
@@ -92,7 +103,12 @@ void cmdTh(int thValue) {
 // GUI以外からディレクトリ追加を指示するコマンド
 // 設定ファイル(INI)を更新した上で、バックグラウンドのワーカプロセスをデタッチ起動する
 void cmdAdd(const QStringList &dirs) {
-  QString iniPath = QCoreApplication::applicationDirPath() + "/DupFind.ini";
+  QString confPath =
+      QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
+  if (!QDir().exists(confPath)) {
+    QDir().mkpath(confPath);
+  }
+  QString iniPath = confPath + "/settings.ini";
   QSettings settings(iniPath, QSettings::IniFormat);
 
   QStringList existingDirs = settings.value("directories").toStringList();
@@ -130,7 +146,14 @@ void workerAdd(const QStringList &dirs) {
   // GUIが起動しているか確認するための共有メモリ
   QSharedMemory sharedMem("DupFind_GUI_Instance");
 
-  DatabaseManager dbManager("dupfind_cache.db");
+  QString dataPath =
+      QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
+  if (!QDir().exists(dataPath)) {
+    QDir().mkpath(dataPath);
+  }
+  QString dbPath = dataPath + "/dupfind_cache.db";
+
+  DatabaseManager dbManager(dbPath.toStdString());
   if (!dbManager.open())
     return;
 
@@ -213,12 +236,24 @@ void cmdSearch(const QString &imageFile) {
   uint64_t dhash = ImageHasher::calculateDHash(img);
   uint64_t phash = ImageHasher::calculatePHash(img);
 
-  QString iniPath = QCoreApplication::applicationDirPath() + "/DupFind.ini";
+  QString confPath =
+      QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
+  if (!QDir().exists(confPath)) {
+    QDir().mkpath(confPath);
+  }
+  QString iniPath = confPath + "/settings.ini";
   QSettings settings(iniPath, QSettings::IniFormat);
   int threshold = settings.value("threshold", 5).toInt();
   bool strict = settings.value("strict_mode", false).toBool();
 
-  DatabaseManager dbManager("dupfind_cache.db");
+  QString dataPath =
+      QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
+  if (!QDir().exists(dataPath)) {
+    QDir().mkpath(dataPath);
+  }
+  QString dbPath = dataPath + "/dupfind_cache.db";
+
+  DatabaseManager dbManager(dbPath.toStdString());
   if (!dbManager.open())
     return;
 
@@ -237,7 +272,12 @@ void cmdSearch(const QString &imageFile) {
 }
 
 void cmdStrict(bool strict) {
-  QString iniPath = QCoreApplication::applicationDirPath() + "/DupFind.ini";
+  QString confPath =
+      QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
+  if (!QDir().exists(confPath)) {
+    QDir().mkpath(confPath);
+  }
+  QString iniPath = confPath + "/settings.ini";
   QSettings settings(iniPath, QSettings::IniFormat);
   settings.setValue("strict_mode", strict);
   std::cout << "Successfully updated strict mode to " << (strict ? "on" : "off")

+ 67 - 42
src/MainWindow.cpp

@@ -1,5 +1,6 @@
 #include "MainWindow.hpp"
 #include "ImageHasher.hpp"
+#include <QDir>
 #include <QDirIterator>
 #include <QFile>
 #include <QFileDialog>
@@ -21,11 +22,18 @@
 #include <QMenu>
 #include <QPointer>
 #include <QSettings>
+#include <QStandardPaths>
 #include <QUrl>
 #include <QtConcurrent>
 
 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
-  m_dbManager = std::make_unique<DatabaseManager>("dupfind_cache.db");
+  QString dataPath =
+      QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
+  if (!QDir().exists(dataPath)) {
+    QDir().mkpath(dataPath);
+  }
+  QString dbPath = dataPath + "/dupfind_cache.db";
+  m_dbManager = std::make_unique<DatabaseManager>(dbPath.toStdString());
   m_dbManager->open();
   m_scanWatcher = new QFutureWatcher<void>(this);
   m_searchWatcher = new QFutureWatcher<std::vector<DuplicateGroup>>(this);
@@ -135,7 +143,8 @@ void MainWindow::setupUi() {
   // Results section (Search box + List View)
   auto *resultLayout = new QVBoxLayout();
   m_searchBox = new QLineEdit();
-  m_searchBox->setPlaceholderText("Filter results by path or filename... (Press Esc to close)");
+  m_searchBox->setPlaceholderText(
+      "Filter results by path or filename... (Press Esc to close)");
   m_searchBox->setVisible(false);
   resultLayout->addWidget(m_searchBox);
 
@@ -145,15 +154,15 @@ void MainWindow::setupUi() {
   m_proxyModel = new ResultFilterProxyModel(this);
   m_proxyModel->setSourceModel(m_model);
   m_delegate = new ResultItemDelegate(this);
-  
+
   m_resultView->setModel(m_proxyModel);
   m_resultView->setItemDelegate(m_delegate);
   m_resultView->setSelectionMode(QAbstractItemView::NoSelection);
   m_resultView->setSpacing(5);
-  
+
   m_resultView->installEventFilter(this);
   m_searchBox->installEventFilter(this);
-  
+
   resultLayout->addWidget(m_resultView);
   splitLayout->addLayout(resultLayout);
 
@@ -173,12 +182,12 @@ void MainWindow::setupUi() {
           &MainWindow::onRemoveDirectory);
   connect(m_startScanBtn, &QPushButton::clicked, this,
           &MainWindow::onStartScan);
-  connect(m_searchBox, &QLineEdit::textChanged, this, &MainWindow::onSearchTextChanged);
+  connect(m_searchBox, &QLineEdit::textChanged, this,
+          &MainWindow::onSearchTextChanged);
   connect(m_clearBtn, &QPushButton::clicked, this, &MainWindow::onClearResults);
 
-  connect(m_deselectBtn, &QPushButton::clicked, this, [this]() {
-    m_model->clearAllChecks();
-  });
+  connect(m_deselectBtn, &QPushButton::clicked, this,
+          [this]() { m_model->clearAllChecks(); });
 
   connect(m_deleteBtn, &QPushButton::clicked, this,
           &MainWindow::onDeleteSelected);
@@ -196,7 +205,13 @@ void MainWindow::setupUi() {
 }
 
 void MainWindow::loadSettings() {
-  QString iniPath = QCoreApplication::applicationDirPath() + "/DupFind.ini";
+  //  QString iniPath = QCoreApplication::applicationDirPath() + "/DupFind.ini";
+  QString dataPath =
+      QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
+  if (!QDir().exists(dataPath)) {
+    QDir().mkpath(dataPath);
+  }
+  QString iniPath = dataPath + "/settings.ini";
   QSettings settings(iniPath, QSettings::IniFormat);
 
   m_currentThreshold = settings.value("threshold", 5).toInt();
@@ -205,7 +220,13 @@ void MainWindow::loadSettings() {
 }
 
 void MainWindow::saveSettings() {
-  QString iniPath = QCoreApplication::applicationDirPath() + "/DupFind.ini";
+  //  QString iniPath = QCoreApplication::applicationDirPath() + "/DupFind.ini";
+  QString dataPath =
+      QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
+  if (!QDir().exists(dataPath)) {
+    QDir().mkpath(dataPath);
+  }
+  QString iniPath = dataPath + "/settings.ini";
   QSettings settings(iniPath, QSettings::IniFormat);
 
   settings.setValue("threshold", m_currentThreshold);
@@ -323,7 +344,9 @@ void MainWindow::onStartScan() {
     auto results = QtConcurrent::blockingMapped(allFiles, processFunc);
 
     // 4. DBへの一括保存 (メインスレッドの管理外のDB接続で行う)
-    DatabaseManager db("dupfind_cache.db");
+    QString dataPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
+    QString dbPath = dataPath + "/dupfind_cache.db";
+    DatabaseManager db(dbPath.toStdString());
     if (db.open()) {
       db.cleanupStaleEntries(); // DBに存在して実体がないファイルを削除
       db.beginTransaction();
@@ -413,19 +436,19 @@ void MainWindow::removeGroupFromView(int groupId) {
   }
 }
 
-void MainWindow::onClearResults() {
-  m_model->clear();
-}
+void MainWindow::onClearResults() { m_model->clear(); }
 
 bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
   if (event->type() == QEvent::KeyPress) {
     QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
     if (obj == m_resultView) {
-      if ((keyEvent->modifiers() & Qt::ControlModifier) && keyEvent->key() == Qt::Key_F) {
+      if ((keyEvent->modifiers() & Qt::ControlModifier) &&
+          keyEvent->key() == Qt::Key_F) {
         m_searchBox->setVisible(true);
         m_searchBox->setFocus();
         return true;
-      } else if (keyEvent->key() == Qt::Key_F && keyEvent->modifiers() == Qt::NoModifier) {
+      } else if (keyEvent->key() == Qt::Key_F &&
+                 keyEvent->modifiers() == Qt::NoModifier) {
         m_searchBox->setVisible(true);
         m_searchBox->setFocus();
         return true;
@@ -448,13 +471,15 @@ void MainWindow::onSearchTextChanged(const QString &text) {
   }
 }
 
-void MainWindow::onFileDoubleClicked(const std::string& path) {
+void MainWindow::onFileDoubleClicked(const std::string &path) {
   if (!path.empty()) {
-    QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path)));
+    QDesktopServices::openUrl(
+        QUrl::fromLocalFile(QString::fromStdString(path)));
   }
 }
 
-void MainWindow::onContextMenuRequested(const std::string& path, int groupId, const QPoint& globalPos) {
+void MainWindow::onContextMenuRequested(const std::string &path, int groupId,
+                                        const QPoint &globalPos) {
   if (!path.empty()) {
     QMenu menu;
     QAction *copyAction = menu.addAction("Copy Full Path(&C)");
@@ -481,9 +506,9 @@ void MainWindow::onDeleteSelected() {
   for (int i = 0; i < m_proxyModel->rowCount(); ++i) {
     QModelIndex proxyIndex = m_proxyModel->index(i, 0);
     QModelIndex sourceIndex = m_proxyModel->mapToSource(proxyIndex);
-    const auto& item = m_model->getItem(sourceIndex.row());
+    const auto &item = m_model->getItem(sourceIndex.row());
     if (item.type == ResultListItem::Header) {
-        visibleGroupIds.insert(item.groupId);
+      visibleGroupIds.insert(item.groupId);
     }
   }
 
@@ -492,22 +517,22 @@ void MainWindow::onDeleteSelected() {
   std::map<int, int> groupCheckedCount;
   std::vector<std::string> pathsToDelete;
 
-  const auto& checkStates = m_model->getCheckStates();
+  const auto &checkStates = m_model->getCheckStates();
 
   int groupId = 0;
-  for (const auto& group : m_currentGroups) {
-      if (visibleGroupIds.find(groupId) != visibleGroupIds.end()) {
-          for (const auto& img : group.images) {
-              groupTotalCount[groupId]++;
-              auto it = checkStates.find(img.path);
-              if (it != checkStates.end() && it->second) {
-                  groupCheckedCount[groupId]++;
-                  count++;
-                  pathsToDelete.push_back(img.path);
-              }
-          }
+  for (const auto &group : m_currentGroups) {
+    if (visibleGroupIds.find(groupId) != visibleGroupIds.end()) {
+      for (const auto &img : group.images) {
+        groupTotalCount[groupId]++;
+        auto it = checkStates.find(img.path);
+        if (it != checkStates.end() && it->second) {
+          groupCheckedCount[groupId]++;
+          count++;
+          pathsToDelete.push_back(img.path);
+        }
       }
-      groupId++;
+    }
+    groupId++;
   }
 
   if (count == 0)
@@ -540,13 +565,13 @@ void MainWindow::onDeleteSelected() {
 
   if (res == QMessageBox::Yes) {
     std::vector<QString> failures;
-    for (const auto& path : pathsToDelete) {
-        QString qPath = QString::fromStdString(path);
-        if (QFile::moveToTrash(qPath)) {
-            m_dbManager->removeImage(path);
-        } else {
-            failures.push_back(qPath);
-        }
+    for (const auto &path : pathsToDelete) {
+      QString qPath = QString::fromStdString(path);
+      if (QFile::moveToTrash(qPath)) {
+        m_dbManager->removeImage(path);
+      } else {
+        failures.push_back(qPath);
+      }
     }
 
     if (!failures.empty()) {