Browse Source

add command line parser

Support open files via command line.
Le Tan 4 years ago
parent
commit
c506b3e1e7

+ 55 - 0
src/commandlineoptions.cpp

@@ -0,0 +1,55 @@
+#include "commandlineoptions.h"
+
+#include <QCommandLineParser>
+#include <QCommandLineOption>
+#include <QCoreApplication>
+#include <QDebug>
+
+#define TR(x) QCoreApplication::translate("main", (x))
+
+CommandLineOptions::ParseResult CommandLineOptions::parse(const QStringList &p_arguments)
+{
+    QCommandLineParser parser;
+    parser.setApplicationDescription(TR("A pleasant note-taking platform."));
+    const auto helpOpt = parser.addHelpOption();
+    const auto versionOpt = parser.addVersionOption();
+
+    // Positional arguments.
+    parser.addPositionalArgument("paths", TR("Files or folders to open."));
+
+    const QCommandLineOption verboseOpt("verbose", TR("Print more logs."));
+    parser.addOption(verboseOpt);
+
+    // WebEngine options.
+    // No need to handle them. Just add them to the parser to avoid parse error.
+    QCommandLineOption webRemoteDebuggingPortOpt("remote-debugging-port",
+                                                 TR("WebEngine remote debugging port."),
+                                                 "port_number");
+    webRemoteDebuggingPortOpt.setFlags(QCommandLineOption::HiddenFromHelp);
+    parser.addOption(webRemoteDebuggingPortOpt);
+
+    if (!parser.parse(p_arguments)) {
+        m_errorMsg = parser.errorText();
+        return ParseResult::Error;
+    }
+
+    // Handle results.
+    m_helpText = parser.helpText();
+    if (parser.isSet(helpOpt)) {
+        return ParseResult::HelpRequested;
+    }
+
+    if (parser.isSet(versionOpt)) {
+        return ParseResult::VersionRequested;
+    }
+
+    // Position arguments.
+    const auto args = parser.positionalArguments();
+    m_pathsToOpen = args;
+
+    if (parser.isSet(verboseOpt)) {
+        m_verbose = true;
+    }
+
+    return ParseResult::Ok;
+}

+ 30 - 0
src/commandlineoptions.h

@@ -0,0 +1,30 @@
+#ifndef COMMANDLINEOPTIONS_H
+#define COMMANDLINEOPTIONS_H
+
+#include <QStringList>
+
+class CommandLineOptions
+{
+public:
+    enum ParseResult
+    {
+        Ok,
+        Error,
+        VersionRequested,
+        HelpRequested
+    };
+
+    CommandLineOptions() = default;
+
+    ParseResult parse(const QStringList &p_arguments);
+
+    QString m_errorMsg;
+
+    QString m_helpText;
+
+    QStringList m_pathsToOpen;
+
+    bool m_verbose = false;
+};
+
+#endif // COMMANDLINEOPTIONS_H

+ 21 - 2
src/core/configmgr.cpp

@@ -229,7 +229,7 @@ QString ConfigMgr::getConfigFilePath(Source p_src) const
     QString configPath;
     switch (p_src) {
     case Source::Default:
-        configPath = QStringLiteral(":/vnotex/data/core/") + c_configFileName;
+        configPath = getDefaultConfigFilePath();
         break;
 
     case Source::App:
@@ -255,9 +255,13 @@ QString ConfigMgr::getConfigFilePath(Source p_src) const
     return configPath;
 }
 
-QSharedPointer<ConfigMgr::Settings> ConfigMgr::getSettings(Source p_src) const
+QString ConfigMgr::getDefaultConfigFilePath()
 {
+    return QStringLiteral(":/vnotex/data/core/") + c_configFileName;
+}
 
+QSharedPointer<ConfigMgr::Settings> ConfigMgr::getSettings(Source p_src) const
+{
     return ConfigMgr::Settings::fromFile(getConfigFilePath(p_src));
 }
 
@@ -433,3 +437,18 @@ QString ConfigMgr::getDocumentOrHomePath()
 
     return docHomePath;
 }
+
+QString ConfigMgr::getApplicationVersion()
+{
+    static QString appVersion;
+
+    if (appVersion.isEmpty()) {
+        auto defaultSettings = ConfigMgr::Settings::fromFile(getDefaultConfigFilePath());
+        const auto &defaultObj = defaultSettings->getJson();
+
+        auto metaDataObj = defaultObj.value(QStringLiteral("metadata")).toObject();
+        appVersion = metaDataObj.value(QStringLiteral("version")).toString();
+    }
+
+    return appVersion;
+}

+ 4 - 0
src/core/configmgr.h

@@ -104,6 +104,8 @@ namespace vnotex
 
         static QString getDocumentOrHomePath();
 
+        static QString getApplicationVersion();
+
         static const QString c_orgName;
 
         static const QString c_appName;
@@ -129,6 +131,8 @@ namespace vnotex
         // Update it if in need.
         void checkAppConfig();
 
+        static QString getDefaultConfigFilePath();
+
         QScopedPointer<MainConfig> m_config;;
 
         // Session config.

+ 48 - 23
src/core/singleinstanceguard.cpp

@@ -12,6 +12,8 @@ using namespace vnotex;
 
 const QString SingleInstanceGuard::c_serverName = "vnote";
 
+const QChar SingleInstanceGuard::c_stringListSeparator = '>';
+
 SingleInstanceGuard::SingleInstanceGuard()
 {
     qInfo() << "guarding is on";
@@ -54,6 +56,14 @@ void SingleInstanceGuard::requestOpenFiles(const QStringList &p_files)
     if (p_files.isEmpty()) {
         return;
     }
+
+    Q_ASSERT(!m_online);
+    if (!m_client || m_client->state() != QLocalSocket::ConnectedState) {
+        qWarning() << "failed to request open files" << m_client->errorString();
+        return ;
+    }
+
+    sendRequest(m_client.data(), OpCode::OpenFiles, p_files.join(c_stringListSeparator));
 }
 
 void SingleInstanceGuard::requestShow()
@@ -157,34 +167,49 @@ void SingleInstanceGuard::receiveCommand(QLocalSocket *p_socket)
     inStream.setDevice(p_socket);
     inStream.setVersion(QDataStream::Qt_5_12);
 
-    if (m_command.m_opCode == OpCode::Null) {
-        // Relies on the fact that QDataStream serializes a quint32 into
-        // sizeof(quint32) bytes.
-        if (p_socket->bytesAvailable() < (int)sizeof(quint32) * 2) {
-            return;
+    while (p_socket->bytesAvailable() > 0) {
+        if (m_command.m_opCode == OpCode::Null) {
+            // Relies on the fact that QDataStream serializes a quint32 into
+            // sizeof(quint32) bytes.
+            if (p_socket->bytesAvailable() < (int)sizeof(quint32) * 2) {
+                return;
+            }
+
+            quint32 opCode = 0;
+            inStream >> opCode;
+            m_command.m_opCode = static_cast<OpCode>(opCode);
+            inStream >> m_command.m_size;
         }
 
-        quint32 opCode = 0;
-        inStream >> opCode;
-        m_command.m_opCode = static_cast<OpCode>(opCode);
-        inStream >> m_command.m_size;
-    }
-
-    if (p_socket->bytesAvailable() < m_command.m_size) {
-        return;
-    }
+        if (p_socket->bytesAvailable() < m_command.m_size) {
+            return;
+        }
 
-    qDebug() << "op code" << m_command.m_opCode << m_command.m_size;
+        qDebug() << "op code" << m_command.m_opCode << m_command.m_size << p_socket->bytesAvailable();
+
+        switch (m_command.m_opCode) {
+        case OpCode::Show:
+            Q_ASSERT(m_command.m_size == 0);
+            emit showRequested();
+            break;
+
+        case OpCode::OpenFiles:
+        {
+            Q_ASSERT(m_command.m_size != 0);
+            QString payload;
+            inStream >> payload;
+            const auto files = payload.split(c_stringListSeparator);
+            emit openFilesRequested(files);
+            break;
+        }
 
-    switch (m_command.m_opCode) {
-    case OpCode::Show:
-        Q_ASSERT(m_command.m_size == 0);
-        emit showRequested();
-        break;
+        default:
+            qWarning() << "unknown op code" << m_command.m_opCode;
+            m_command.clear();
+            return;
+        }
 
-    default:
-        qWarning() << "unknown op code" << m_command.m_opCode;
-        break;
+        m_command.clear();
     }
 }
 

+ 4 - 1
src/core/singleinstanceguard.h

@@ -41,7 +41,8 @@ namespace vnotex
         enum OpCode
         {
             Null = 0,
-            Show
+            Show,
+            OpenFiles
         };
 
         struct Command
@@ -78,6 +79,8 @@ namespace vnotex
         Command m_command;
 
         static const QString c_serverName;
+
+        static const QChar c_stringListSeparator;
     };
 } // ns vnotex
 

+ 44 - 5
src/main.cpp

@@ -21,6 +21,7 @@
 #include <QWebEngineSettings>
 #include <core/exception.h>
 #include <widgets/messageboxhelper.h>
+#include "commandlineoptions.h"
 
 using namespace vnotex;
 
@@ -28,6 +29,8 @@ void loadTranslators(QApplication &p_app);
 
 void initWebEngineSettings();
 
+void showMessageOnCommandLineIfAvailable(const QString &p_msg);
+
 int main(int argc, char *argv[])
 {
     QTextCodec *codec = QTextCodec::codecForName("UTF8");
@@ -76,18 +79,44 @@ int main(int argc, char *argv[])
 
         app.setApplicationName(ConfigMgr::c_appName);
         app.setOrganizationName(ConfigMgr::c_orgName);
+
+        app.setApplicationVersion(ConfigMgr::getApplicationVersion());
+    }
+
+    CommandLineOptions cmdOptions;
+    switch (cmdOptions.parse(app.arguments())) {
+    case CommandLineOptions::Ok:
+        break;
+
+    case CommandLineOptions::Error:
+        showMessageOnCommandLineIfAvailable(cmdOptions.m_errorMsg);
+        return -1;
+
+    case CommandLineOptions::VersionRequested:
+    {
+        auto versionStr = QString("%1 %2").arg(app.applicationName()).arg(app.applicationVersion());
+        showMessageOnCommandLineIfAvailable(versionStr);
+        return 0;
+    }
+
+    case CommandLineOptions::HelpRequested:
+        Q_FALLTHROUGH();
+    default:
+        showMessageOnCommandLineIfAvailable(cmdOptions.m_helpText);
+        return 0;
     }
 
     // Guarding.
     SingleInstanceGuard guard;
     bool canRun = guard.tryRun();
     if (!canRun) {
+        guard.requestOpenFiles(cmdOptions.m_pathsToOpen);
         guard.requestShow();
         return 0;
     }
 
     try {
-        app.setApplicationVersion(ConfigMgr::getInst().getConfig().getVersion());
+        ConfigMgr::getInst();
     } catch (Exception &e) {
         MessageBoxHelper::notify(MessageBoxHelper::Critical,
                                  MainWindow::tr("%1 failed to start.").arg(ConfigMgr::c_appName),
@@ -98,7 +127,7 @@ int main(int argc, char *argv[])
     }
 
     // Init logger after app info is set.
-    Logger::init(false);
+    Logger::init(cmdOptions.m_verbose);
 
     qInfo() << QString("%1 (v%2) started at %3 (%4)").arg(ConfigMgr::c_appName,
                                                           app.applicationVersion(),
@@ -112,8 +141,6 @@ int main(int argc, char *argv[])
         qWarning() << "versions of the built and linked OpenSSL mismatch, network may not work";
     }
 
-    // TODO: parse command line options.
-
     // Should set the correct locale before VNoteX::getInst().
     loadTranslators(app);
 
@@ -131,8 +158,10 @@ int main(int argc, char *argv[])
 
     QObject::connect(&guard, &SingleInstanceGuard::showRequested,
                      &window, &MainWindow::showMainWindow);
+    QObject::connect(&guard, &SingleInstanceGuard::openFilesRequested,
+                     &window, &MainWindow::openFiles);
 
-    window.kickOffOnStart();
+    window.kickOffOnStart(cmdOptions.m_pathsToOpen);
 
     int ret = app.exec();
     if (ret == RESTART_EXIT_CODE) {
@@ -208,3 +237,13 @@ void initWebEngineSettings()
     auto settings = QWebEngineSettings::defaultSettings();
     settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
 }
+
+void showMessageOnCommandLineIfAvailable(const QString &p_msg)
+{
+#if defined(Q_OS_WIN)
+    MessageBoxHelper::notify(MessageBoxHelper::Information,
+                             QString("<pre>%1</pre>").arg(p_msg));
+#else
+    printf("%s\n", qPrintable(p_msg));
+#endif
+}

+ 4 - 0
src/src.pro

@@ -30,6 +30,7 @@ ICON = data/core/icons/vnote.icns
 TRANSLATIONS += data/core/translations/vnote_zh_CN.ts
 
 SOURCES += \
+    commandlineoptions.cpp \
     main.cpp
 
 INCLUDEPATH *= $$PWD
@@ -137,3 +138,6 @@ unix:!macx {
     INSTALLS += extraresource
     message("VNote will be installed in prefix $${PREFIX}")
 }
+
+HEADERS += \
+    commandlineoptions.h

+ 16 - 5
src/widgets/mainwindow.cpp

@@ -70,15 +70,26 @@ MainWindow::~MainWindow()
     m_viewArea = nullptr;
 }
 
-void MainWindow::kickOffOnStart()
+void MainWindow::kickOffOnStart(const QStringList &p_paths)
 {
-    VNoteX::getInst().initLoad();
+    QTimer::singleShot(300, [this, p_paths]() {
+        VNoteX::getInst().initLoad();
 
-    emit mainWindowStarted();
+        emit mainWindowStarted();
 
-    emit layoutChanged();
+        emit layoutChanged();
 
-    demoWidget();
+        demoWidget();
+
+        openFiles(p_paths);
+    });
+}
+
+void MainWindow::openFiles(const QStringList &p_files)
+{
+    for (const auto &file : p_files) {
+        emit VNoteX::getInst().openFileRequested(file, QSharedPointer<FileOpenParameters>::create());
+    }
 }
 
 void MainWindow::setupUI()

+ 3 - 1
src/widgets/mainwindow.h

@@ -33,7 +33,7 @@ namespace vnotex
         MainWindow(const MainWindow &) = delete;
         void operator=(const MainWindow &) = delete;
 
-        void kickOffOnStart();
+        void kickOffOnStart(const QStringList &p_paths);
 
         void resetStateAndGeometry();
 
@@ -53,6 +53,8 @@ namespace vnotex
 
         void quitApp();
 
+        void openFiles(const QStringList &p_files);
+
     signals:
         void mainWindowStarted();