From ff07f13cd67a862969ec0c9f13d98ff3e8505ad0 Mon Sep 17 00:00:00 2001 From: skilion Date: Mon, 14 Sep 2015 19:21:06 +0200 Subject: [PATCH] command line options --- Makefile | 2 +- src/config.d | 19 +++++---- src/main.d | 105 ++++++++++++++++++++++++++++++++++--------------- src/onedrive.d | 23 +++++------ 4 files changed, 96 insertions(+), 53 deletions(-) diff --git a/Makefile b/Makefile index ceaacaaa..a298d05b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ DC = dmd -DFLAGS = -debug -g -gs -od./bin -of./bin/$@ -L-lcurl -L-lsqlite3 -L-ldl +DFLAGS = -unittest -debug -g -gs -od./bin -of./bin/$@ -L-lcurl -L-lsqlite3 -L-ldl SOURCES = \ /usr/include/dlang/dmd/core/sys/posix/poll.d \ diff --git a/src/config.d b/src/config.d index def78456..762be3ff 100644 --- a/src/config.d +++ b/src/config.d @@ -1,6 +1,6 @@ -import std.regex, std.stdio, std.file; +import std.file, std.regex, std.stdio; -final class Config +struct Config { private string filename; private string[string] values; @@ -8,12 +8,16 @@ final class Config this(string filename) { this.filename = filename; - load(); } string get(string key) { - return values[key]; + import core.exception; + try { + return values[key]; + } catch (RangeError e) { + throw new Exception("Missing config value: " ~ key); + } } void set(string key, string value) @@ -24,16 +28,17 @@ final class Config void load() { values = null; - scope (failure) return; auto file = File(filename, "r"); auto r = regex("(?:^\\s*)(\\w+)(?:\\s*=\\s*\")(.*)(?:\"\\s*$)"); foreach (line; file.byLine()) { auto c = matchFirst(line, r); if (!c.empty) { - c.popFront(); // skip whole match + c.popFront(); // skip the whole match string key = c.front.dup; c.popFront(); values[key] = c.front.dup; + } else { + writeln("Malformed config line: ", line); } } } @@ -53,7 +58,7 @@ final class Config unittest { - auto cfg = new Config("/tmp/test.conf"); + auto cfg = Config(tempDir() ~ "/test.conf"); cfg.set("test1", "1"); cfg.set("test2", "2"); cfg.set("test1", "3"); diff --git a/src/main.d b/src/main.d index 1b61079c..f9586294 100644 --- a/src/main.d +++ b/src/main.d @@ -1,46 +1,89 @@ -import std.file; +import std.getopt, std.file, std.process, std.stdio; import config, monitor, onedrive, sync; -private string configFile = "./onedrive.conf"; -private string refreshTokenFile = "refresh_token"; +string ver = "1.0"; -void main() +void main(string[] args) { - auto cfg = new Config(configFile); - - auto onedrive = new OneDriveApi(cfg.get("client_id"), cfg.get("client_secret")); - onedrive.onRefreshToken = (string refreshToken) { std.file.write(refreshTokenFile, refreshToken); }; + bool monitor, resync, resetLocal, resetRemote, verbose; try { - string refreshToken = readText(refreshTokenFile); + writeln("OneDrive Client for Linux v", ver); + auto opt = getopt( + args, + "monitor|m", "Keep monitoring for local and remote changes.", &monitor, + "resync", "Perform a full synchronization.", &resync, + "verbose|v", "Print more details, useful for debugging.", &verbose + ); + if (opt.helpWanted) { + defaultGetoptPrinter("Available options:", opt.options); + return; + } + } catch (GetOptException e) { + writeln(e.msg); + writeln("Try 'onedrive -h' for more information."); + return; + } + + string homeDirName = environment["HOME"]; + string configDirName = environment.get("XDG_CONFIG_HOME", homeDirName ~ "/.config") ~ "/onedrive"; + string configFilePath = configDirName ~ "/config"; + string refreshTokenFilePath = configDirName ~ "/refresh_token"; + string statusTokenFilePath = configDirName ~ "/status_token"; + string databaseFilePath = configDirName ~ "/database"; + + if (resync || resetLocal || resetRemote) { + if (verbose) writeln("Deleting the current status ..."); + if (exists(databaseFilePath)) remove(databaseFilePath); + if (exists(statusTokenFilePath)) remove(statusTokenFilePath); + } + + if (verbose) writeln("Loading config ..."); + auto cfg = config.Config(configFilePath); + cfg.load(); + + if (verbose) writeln("Initializing the OneDrive API ..."); + auto onedrive = new OneDriveApi(cfg, verbose); + onedrive.onRefreshToken = (string refreshToken) { + std.file.write(refreshTokenFilePath, refreshToken); + }; + try { + string refreshToken = readText(refreshTokenFilePath); onedrive.setRefreshToken(refreshToken); } catch (FileException e) { onedrive.authorize(); } + // TODO check if the token is valid + if (verbose) writeln("Initializing the Synchronization Engine ..."); auto sync = new SyncEngine(cfg, onedrive); sync.applyDifferences(); sync.uploadDifferences(); - Monitor m; - import std.stdio; - m.onDirCreated = delegate(string path) { - writeln("Directory created: ", path); - sync.createFolderItem(path); - sync.uploadDifferences(path); - }; - m.onFileChanged = delegate(string path) { - writeln("File changed: ", path); - sync.uploadDifference2(path); - }; - m.onDelete = delegate(string path) { - sync.deleteByPath(path); - }; - m.onMove = delegate(string from, string to) { - sync.moveItem(from, to); - }; - m.init(); - string syncDir = cfg.get("sync_dir"); - chdir(syncDir); - m.addRecursive("test"); - while (true) m.update(); + if (monitor) { + if (verbose) writeln("Monitoring for changes ..."); + Monitor m; + m.onDirCreated = delegate(string path) { + if (verbose) writeln("[M] Directory created: ", path); + sync.createFolderItem(path); + sync.uploadDifferences(path); + }; + m.onFileChanged = delegate(string path) { + if (verbose) writeln("[M] File changed: ", path); + sync.uploadDifference2(path); + }; + m.onDelete = delegate(string path) { + if (verbose) writeln("[M] Item deleted: ", path); + sync.deleteByPath(path); + }; + m.onMove = delegate(string from, string to) { + if (verbose) writeln("[M] Item moved: ", from, " -> ", to); + sync.moveItem(from, to); + }; + m.init(); + string syncDir = cfg.get("sync_dir"); + chdir(syncDir); + m.addRecursive("test"); + while (true) m.update(); + // TODO download changes + } } diff --git a/src/onedrive.d b/src/onedrive.d index 9b97d10f..665aac7d 100644 --- a/src/onedrive.d +++ b/src/onedrive.d @@ -1,6 +1,5 @@ -module onedrive; - import std.datetime, std.json, std.net.curl, std.path, std.string, std.uni, std.uri; +import config; private immutable { string authUrl = "https://login.live.com/oauth20_authorize.srf"; @@ -30,18 +29,14 @@ final class OneDriveApi private SysTime accessTokenExpiration; private HTTP http; - void function(string) onRefreshToken; // called when a new refresh_token is received + void delegate(string) onRefreshToken; // called when a new refresh_token is received - this(string clientId, string clientSecret) + this(Config cfg, bool verbose) { - this.clientId = clientId; - this.clientSecret = clientSecret; + this.clientId = cfg.get("client_id"); + this.clientSecret = cfg.get("client_secret"); http = HTTP(); - //debug http.verbose = true; - // HACK: prevent SIGPIPE - //import etc.c.signal, etc.c.curl; - //http.handle.set(CurlOption.nosignal, 0); - //signal(/*SIGPIPE*/ 13, /*SIG_IGN*/ cast(void function(int)) 1); + http.verbose = verbose; } ~this() @@ -53,8 +48,8 @@ final class OneDriveApi { import std.stdio, std.regex; string url = authUrl ~ "?client_id=" ~ clientId ~ "&scope=wl.offline_access onedrive.readwrite&response_type=code&redirect_url=" ~ redirectUrl; - writeln("Authorize this app visiting:"); - writeln(url); + writeln("Authorize this app visiting:\n"); + writeln(url, "\n"); while (true) { char[] response; @@ -62,7 +57,7 @@ final class OneDriveApi readln(response); auto c = matchFirst(response, r"(?:code=)(([\w\d]+-){4}[\w\d]+)"); if (!c.empty) { - c.popFront(); // skip whole match + c.popFront(); // skip the whole match redeemToken(c.front); break; }