1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00
joplin/QtClient/JoplinQtClient/cliapplication.cpp
2017-03-01 21:01:03 +00:00

487 lines
13 KiB
C++
Executable File

#include <stable.h>
#include "cliapplication.h"
#include "constants.h"
#include "database.h"
#include "paths.h"
#include "uuid.h"
#include "settings.h"
#include "models/folder.h"
#include <signal.h>
namespace jop {
StdoutHandler::StdoutHandler() : QTextStream(stdout) {}
StderrHandler::StderrHandler() : QTextStream(stderr) {}
CliApplication::CliApplication(int &argc, char **argv) : QCoreApplication(argc, argv) {
// This is linked to where the QSettings will be saved. In other words,
// if these values are changed, the settings will be reset and saved
// somewhere else.
QCoreApplication::setOrganizationName(jop::ORG_NAME);
QCoreApplication::setOrganizationDomain(jop::ORG_DOMAIN);
QCoreApplication::setApplicationName(jop::APP_NAME);
qInfo() << "Config dir:" << paths::configDir();
qInfo() << "Database file:" << paths::databaseFile();
qInfo() << "SSL:" << QSslSocket::sslLibraryBuildVersionString() << QSslSocket::sslLibraryVersionNumber();
jop::db().initialize(paths::databaseFile());
Settings::initialize();
Settings settings;
if (!settings.contains("clientId")) {
// Client ID should be unique per instance of a program
settings.setValue("clientId", uuid::createUuid());
}
connect(&api_, SIGNAL(requestDone(const QJsonObject&, const QString&)), this, SLOT(api_requestDone(const QJsonObject&, const QString&)));
connect(&synchronizer_, SIGNAL(started()), this, SLOT(synchronizer_started()));
connect(&synchronizer_, SIGNAL(finished()), this, SLOT(synchronizer_finished()));
}
CliApplication::~CliApplication() {
jop::db().close();
}
void CliApplication::api_requestDone(const QJsonObject& response, const QString& tag) {
// TODO: handle errors
// Handle expired sessions
if (tag == "getSession") {
if (response.contains("error")) {
qStderr() << "Could not login: " << response.value("error").toString() << endl;
emit synchronizationDone();
} else {
QString sessionId = response.value("id").toString();
Settings settings;
settings.setValue("session.id", sessionId);
startSynchronization();
}
}
}
// Call this only once the API base URL has been defined and the session has been set.
void CliApplication::startSynchronization() {
Settings settings;
synchronizer_.api().setBaseUrl(api_.baseUrl());
synchronizer_.setSessionId(settings.value("session.id").toString());
synchronizer_.unfreeze();
synchronizer_.start();
}
void CliApplication::synchronizer_started() {
qDebug() << "Synchronization started...";
}
void CliApplication::synchronizer_finished() {
qDebug() << "Synchronization finished...";
emit synchronizationDone();
}
bool CliApplication::filePutContents(const QString& filePath, const QString& content) const {
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return false;
QTextStream out(&file);
out << content;
out.flush();
return true;
}
QString CliApplication::fileGetContents(const QString& filePath) const {
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return QString("");
QTextStream in(&file);
return in.readAll();
}
void CliApplication::saveNoteIfFileChanged(Note& note, const QDateTime& originalLastModified, const QString& noteFilePath) {
if (originalLastModified == QFileInfo(noteFilePath).lastModified()) return;
QString content = fileGetContents(noteFilePath);
if (content.isEmpty()) return;
note.patchFriendlyString(content);
note.save();
}
// int CliApplication::execCommandConfig(QCommandLineParser& parser) {
// parser.addPositionalArgument("key", "Key of the config property.");
// parser.addPositionalArgument("value", "Value of the config property.");
// QCommandLineOption unsetOption(QStringList() << "unset", "Unset the given <key>.", "key");
// parser.addOption(unsetOption);
// QStringList args = parser.positionalArguments();
// Settings settings;
// QString propKey = args.size() >= 1 ? args[0] : "";
// QString propValue = args.size() >= 2 ? args[1] : "";
// if (propKey.isEmpty()) {
// QStringList propKeys = settings.allKeys();
// for (int i = 0; i < propKeys.size(); i++) {
// qStdout() << settings.keyValueToFriendlyString(propKeys[i]) << endl;
// }
// return 0;
// }
// if (propValue.isEmpty()) {
// qStdout() << settings.keyValueToFriendlyString(propKey) << endl;
// return 0;
// }
// settings.setValue(propKey, propValue);
// return 0;
// }
QStringList CliApplication::parseCommandLinePath(const QString& commandLine) const {
QStringList output;
int state = 0; // 0 = "outside quotes", 1 = "inside quotes"
QString current("");
for (int i = 0; i < commandLine.length(); i++) {
QChar c = commandLine[i];
// End quote
if (c == '"' && state == 1) {
output << current;
current = "";
state = 0;
continue;
}
// Start quote
if (c == '"' && state == 0) {
state = 1;
current = current.trimmed();
if (current != "") output << current;
current = "";
state = 1;
continue;
}
// A space when not inside a quoted string
if (c == ' ' && state == 0) {
current = current.trimmed();
if (current != "") output << current;
current = "";
continue;
}
current += c;
}
if (state == 0) current = current.trimmed();
if (current != "") output << current;
return output;
}
QString CliApplication::commandLineArgsToString(const QStringList& args) const {
QString output;
for (int i = 0; i < args.size(); i++) {
if (output != "") output += " ";
QString arg = args[i];
if (arg.contains(' ')) {
output += QString("\"%1\"").arg(arg);
} else {
output += arg;
}
}
return output;
}
int CliApplication::exec() {
qDebug() << "===========================================";
Settings settings;
QString command = "help";
QStringList args = arguments();
if (args.size() >= 2) {
command = args[1];
args.erase(args.begin() + 1);
}
QCommandLineParser parser;
QCommandLineOption helpOption(QStringList() << "h" << "help", "Display usage information.");
parser.addOption(helpOption);
parser.addVersionOption();
// mkdir "new_folder"
// rm "new_folder"
// ls
// ls new_folder
// touch new_folder/new_note
// edit new_folder/new_note
// config editor "subl -w %1"
// sync
// TODO: implement mv "new_folder"
if (command == "mkdir") {
parser.addPositionalArgument("path", "Folder path.");
} else if (command == "rm") {
parser.addPositionalArgument("path", "Folder path.");
} else if (command == "ls") {
parser.addPositionalArgument("path", "Folder path.");
} else if (command == "touch") {
parser.addPositionalArgument("path", "Note path.");
} else if (command == "edit") {
parser.addPositionalArgument("path", "Note path.");
} else if (command == "config") {
parser.addPositionalArgument("key", "Key of the config property.");
parser.addPositionalArgument("value", "Value of the config property.");
parser.addOption(QCommandLineOption(QStringList() << "unset", "Unset the given <key>.", "key"));
} else if (command == "sync") {
} else if (command == "help") {
} else {
qStderr() << parser.helpText() << endl;
return 1;
}
parser.process(args);
if (parser.isSet(helpOption) || command == "help") {
qStdout() << parser.helpText();
return 0;
}
args = parser.positionalArguments();
int errorCode = 0;
if (command == "mkdir") {
QString path = args.size() ? args[0] : QString();
if (path.isEmpty()) {
qStderr() << "Please provide a path or name for the folder.";
return 1;
}
std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, false, errorCode);
if (errorCode) {
qStderr() << "Invalid path: " << path << endl;
return 1;
}
Folder folder;
folder.setValue("parent_id", folders.size() ? folders[folders.size() - 1]->idString() : "");
folder.setValue("title", Folder::pathBaseName(path));
folder.save();
}
if (command == "rm") {
QString path = args.size() ? args[0] : QString();
if (path.isEmpty()) {
qStderr() << "Please provide a path or name for the folder.";
return 1;
}
std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, true, errorCode);
if (errorCode || !folders.size()) {
qStderr() << "Invalid path: " << path << endl;
return 1;
}
folders[folders.size() - 1]->dispose();
}
if (command == "ls") {
QString path = args.size() ? args[0] : QString();
std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, true, errorCode);
if (errorCode) {
qStderr() << "Invalid path: " << path << endl;
return 1;
}
std::vector<std::unique_ptr<BaseModel>> children;
if (folders.size()) {
children = folders[folders.size() - 1]->children();
} else {
std::unique_ptr<Folder> root = Folder::root();
children = root->children();
}
qStdout() << QString("Total: %1 items").arg(children.size()) << endl;
for (size_t i = 0; i < children.size(); i++) {
qStdout() << children[i]->displayTitle() << endl;
}
}
if (command == "touch") {
QString path = args.size() ? args[0] : QString();
if (path.isEmpty()) {
qStderr() << "Please provide a path or name for the note.";
return 1;
}
std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, false, errorCode);
if (errorCode) {
qStderr() << "Invalid path: " << path << endl;
} else {
QString noteTitle = Folder::pathBaseName(path);
Note note;
note.setValue("parent_id", folders.size() ? folders[folders.size() - 1]->idString() : "");
note.setValue("title", noteTitle);
note.save();
}
}
if (command == "edit") {
QString path = args.size() ? args[0] : QString();
if (path.isEmpty()) {
qStderr() << "Please provide a path or name for the note.";
return 1;
}
std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, false, errorCode);
if (errorCode) {
qStderr() << "Invalid path: " << path << endl;
} else {
// TODO: handle case where two notes with the same title exist
QString editorCommandString = settings.value("editor").toString().trimmed();
if (editorCommandString.isEmpty()) {
qStderr() << "No editor is defined. Please define one using the \"config editor\" command." << endl;
return 1;
}
QStringList editorCommand = parseCommandLinePath(editorCommandString);
QString parentId = folders.size() ? folders[folders.size() - 1]->idString() : QString("");
QString noteTitle = Folder::pathBaseName(path);
Note note;
if (!note.loadByField(parentId, QString("title"), noteTitle)) {
note.setValue("parent_id", folders.size() ? folders[folders.size() - 1]->idString() : "");
note.setValue("title", noteTitle);
note.save();
note.reload(); // To ensure that all fields are populated with the default values
}
QString noteFilePath = QString("%1/%2.txt").arg(paths::noteDraftsDir()).arg(note.idString());
if (!filePutContents(noteFilePath, note.toFriendlyString())) {
qStderr() << QString("Cannot open %1 for writing").arg(noteFilePath) << endl;
return 1;
}
QFileInfo fileInfo(noteFilePath);
QDateTime originalLastModified = fileInfo.lastModified();
qStdout() << QString("Editing note \"%1\" (Either close the editor or press Ctrl+C when done)").arg(path) << endl;
qDebug() << "File:" << noteFilePath;
QProcess* process = new QProcess();
qint64 processId = 0;
QString editorCommandPath = editorCommand.takeFirst();
editorCommand << noteFilePath;
if (!process->startDetached(editorCommandPath, editorCommand, QString(), &processId)) {
qStderr() << QString("Could not start command: %1").arg(editorCommandPath + " " + commandLineArgsToString(editorCommand)) << endl;
return 1;
}
while (kill(processId, 0) == 0) { // While the process still exist
QThread::sleep(2);
saveNoteIfFileChanged(note, originalLastModified, noteFilePath);
}
saveNoteIfFileChanged(note, originalLastModified, noteFilePath);
delete process; process = NULL;
QFile::remove(noteFilePath);
}
}
if (command == "config") {
if (parser.isSet("unset")) {
QString key = parser.value("unset").trimmed();
settings.remove(key);
return 0;
}
QString propKey = args.size() >= 1 ? args[0] : "";
QString propValue = args.size() >= 2 ? args[1] : "";
if (propKey.isEmpty()) {
QStringList propKeys = settings.allKeys();
for (int i = 0; i < propKeys.size(); i++) {
qStdout() << settings.keyValueToFriendlyString(propKeys[i]) << endl;
}
return 0;
}
if (propValue.isEmpty()) {
qStdout() << settings.keyValueToFriendlyString(propKey) << endl;
return 0;
}
settings.setValue(propKey, propValue);
}
if (command == "sync") {
QString sessionId = settings.value("session.id").toString();
qDebug() << "Session ID:" << sessionId;
// TODO: ask user
api_.setBaseUrl("http://127.0.0.1:8000");
QEventLoop loop;
connect(this, SIGNAL(synchronizationDone()), &loop, SLOT(quit()));
if (sessionId == "") {
QTextStream qtin(stdin);
qStdout() << "Enter email:" << endl;
QString email = qtin.readLine();
qStdout() << "Enter password:" << endl;
QString password = qtin.readLine();
qDebug() << email << password;
Settings settings;
QUrlQuery postData;
postData.addQueryItem("email", email);
postData.addQueryItem("password", password);
postData.addQueryItem("client_id", settings.value("clientId").toString());
api_.post("sessions", QUrlQuery(), postData, "getSession");
} else {
startSynchronization();
}
loop.exec();
qDebug() << "Synchronization done";
}
qDebug() << "=========================================== END";
return 0;
}
}