1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-09-16 08:56:40 +02:00

Handle editing notes and setting config values

This commit is contained in:
Laurent Cozic
2017-02-10 21:14:21 +00:00
parent 0a9b9f42b5
commit 964c3997fe
8 changed files with 214 additions and 38 deletions

View File

@@ -23,6 +23,7 @@
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,
@@ -83,9 +84,107 @@ QString CliApplication::fileGetContents(const QString& filePath) const {
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();
@@ -95,7 +194,8 @@ int CliApplication::exec() {
}
QCommandLineParser parser;
parser.addHelpOption();
QCommandLineOption helpOption(QStringList() << "h" << "help", "Display usage information.");
parser.addOption(helpOption);
parser.addVersionOption();
// mkdir "new_folder"
@@ -103,6 +203,7 @@ int CliApplication::exec() {
// ls new_folder
// touch new_folder/new_note
// edit new_folder/new_note
// config editor "subl -w %1"
if (command == "mkdir") {
parser.addPositionalArgument("path", "Folder path.");
@@ -112,13 +213,24 @@ int CliApplication::exec() {
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 == "help") {
} else {
qDebug().noquote() << parser.helpText();
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;
@@ -127,13 +239,13 @@ int CliApplication::exec() {
QString path = args.size() ? args[0] : QString();
if (path.isEmpty()) {
qStdout() << "Please provide a path or name for the folder.";
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) {
qStdout() << "Invalid path: " << path << endl;
qStderr() << "Invalid path: " << path << endl;
return 1;
}
@@ -148,7 +260,7 @@ int CliApplication::exec() {
std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, true, errorCode);
if (errorCode) {
qStdout() << "Invalid path: " << path << endl;
qStderr() << "Invalid path: " << path << endl;
return 1;
}
@@ -170,14 +282,14 @@ int CliApplication::exec() {
QString path = args.size() ? args[0] : QString();
if (path.isEmpty()) {
qStdout() << "Please provide a path or name for the note.";
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) {
qStdout() << "Invalid path: " << path << endl;
qStderr() << "Invalid path: " << path << endl;
} else {
QString noteTitle = Folder::pathBaseName(path);
@@ -192,55 +304,93 @@ int CliApplication::exec() {
QString path = args.size() ? args[0] : QString();
if (path.isEmpty()) {
qStdout() << "Please provide a path or name for the note.";
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) {
qStdout() << "Invalid path: " << path << endl;
qStderr() << "Invalid path: " << path << endl;
} else {
// TODO: handle case where note doesn't exist
// 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;
note.loadByField(parentId, QString("title"), noteTitle);
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());
bool ok = filePutContents(noteFilePath, note.toFriendlyString());
if (!ok) {
qStdout() << QString("Cannot open %1 for writing").arg(noteFilePath) << endl;
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\" (Press Ctrl+C when done)").arg(path) << endl;
qStdout() << QString("Editing note \"%1\" (Either close the editor or press Ctrl+C when done)").arg(path) << endl;
QProcess* process = new QProcess();
qint64* processId = new qint64();
process->startDetached("/usr/bin/vim", QStringList() << noteFilePath, QString(), processId);
while (kill(*processId, 0) == 0) {
QThread::sleep(1);
}
delete processId;
QString content = fileGetContents(noteFilePath);
if (content.isEmpty()) {
qStdout() << QString("Cannot open %1 for reading, or file is empty.").arg(noteFilePath) << endl;
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;
}
fileInfo.refresh();
qDebug() << originalLastModified.toString("hh:mm:ss.zzz") << fileInfo.lastModified().toString("hh:mm:ss.zzz");
while (kill(*processId, 0) == 0) {
QThread::sleep(2);
saveNoteIfFileChanged(note, originalLastModified, noteFilePath);
}
delete processId; processId = NULL;
//note.patchFriendlyString(content);
//note.save();
saveNoteIfFileChanged(note, originalLastModified, noteFilePath);
// TODO: delete note file
}
}
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);
}
qDebug() << "=========================================== END";
return 0;

View File

@@ -4,6 +4,7 @@
#include <stable.h>
#include "command.h"
#include "models/note.h"
namespace jop {
@@ -15,11 +16,24 @@ public:
};
class StderrHandler : public QTextStream {
public:
StderrHandler();
};
inline StdoutHandler& qStdout() {
static StdoutHandler r;
return r;
}
inline StderrHandler& qStderr() {
static StderrHandler r;
return r;
}
class CliApplication : public QCoreApplication {
public:
@@ -33,6 +47,9 @@ private:
bool filePutContents(const QString& filePath, const QString& content) const;
QString fileGetContents(const QString& filePath) const;
void saveNoteIfFileChanged(Note& note, const QDateTime& originalLastModified, const QString& noteFilePath);
QStringList parseCommandLinePath(const QString& commandLine) const;
QString commandLineArgsToString(const QStringList& args) const;
};

View File

@@ -26,13 +26,18 @@ int main(int argc, char *argv[]) {
#ifdef JOP_FRONT_END_GUI
qDebug() << "Front end: GUI";
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
jop::Application app(argc, argv);
jop::Application* app = new jop::Application(argc, argv);
#endif
#ifdef JOP_FRONT_END_CLI
qDebug() << "Front end: CLI";
jop::CliApplication app(argc, argv);
jop::CliApplication* app = new jop::CliApplication(argc, argv);
#endif
return app.exec();
int errorCode = app->exec();
delete app;
app = NULL;
return errorCode;
}

View File

@@ -38,7 +38,7 @@ int BaseModel::count(Table table, const QString &parentId) {
bool BaseModel::load(const QString &id) {
QSqlQuery q(*jop::db().database());
q.prepare("SELECT " + BaseModel::tableFieldNames(table()).join(",") + " FROM " + BaseModel::tableName(table()) + " WHERE id = :id");
q.prepare("SELECT " + BaseModel::sqlTableFields(table()) + " FROM " + BaseModel::tableName(table()) + " WHERE id = :id");
q.bindValue(":id", id);
jop::db().execQuery(q);
q.next();
@@ -49,6 +49,10 @@ bool BaseModel::load(const QString &id) {
return true;
}
bool BaseModel::reload() {
return load(idString());
}
bool BaseModel::loadByField(const QString& parentId, const QString& field, const QString& fieldValue) {
QSqlQuery q(*jop::db().database());
QString sql = QString("SELECT %1 FROM %2 WHERE `%3` = :field_value AND parent_id = :parent_id LIMIT 1")

View File

@@ -49,6 +49,7 @@ public:
static int count(jop::Table table, const QString &parentId);
bool load(const QString& id);
bool loadByField(const QString& parentId, const QString& field, const QString& fieldValue);
bool reload();
virtual bool save(bool trackChanges = true);
virtual bool dispose();

View File

@@ -24,7 +24,6 @@ QString Item::toFriendlyString() const {
}
void Item::patchFriendlyString(const QString& patch) {
QHash<QString, QString> values;
QStringList lines = patch.split(jop::NEW_LINE);
QString title("");
@@ -51,17 +50,12 @@ void Item::patchFriendlyString(const QString& patch) {
int colonIndex = line.indexOf(':');
QString propName = line.left(colonIndex).trimmed();
QString propValue = line.right(line.length() - colonIndex - 1).trimmed();
qDebug() << propName << propValue;
setValue(propName, propValue);
}
}
setValue("title", title);
setValue("body", body);
for (QHash<QString, QString>::const_iterator it = values.begin(); it != values.end(); ++it) {
setValue(it.key(), it.value());
}
}
}

View File

@@ -34,3 +34,7 @@ QString Settings::valueString(const QString &name, const QString &defaultValue)
int Settings::valueInt(const QString &name, int defaultValue) {
return value(name, defaultValue).toInt();
}
QString Settings::keyValueToFriendlyString(const QString& key) const {
return QString("%1 = %2").arg(key).arg(value(key).toString());
}

View File

@@ -15,6 +15,7 @@ public:
Settings();
static void initialize();
QString keyValueToFriendlyString(const QString& key) const;
public slots: