diff --git a/Makefile b/Makefile index e6322d59..0164631a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,9 @@ DC ?= dmd -DFLAGS += -w -g -ofonedrive -O -L-lcurl -L-lsqlite3 -L-ldl -J. +ifdef NOTIFICATIONS + DFLAGSNOTIFICATIONS ?= -version=NoPragma -version=NoGdk -version=Notifications \ + -L-lgmodule-2.0 -L-lglib-2.0 -L-lnotify +endif +DFLAGS += -w -g -ofonedrive -O -L-lcurl -L-lsqlite3 $(DFLAGSNOTIFICATIONS) -L-ldl -J. PREFIX ?= /usr/local DOCDIR ?= $(PREFIX)/share/doc/onedrive MANDIR ?= $(PREFIX)/share/man/man1 @@ -26,6 +30,10 @@ SOURCES = \ src/util.d \ src/progress.d +ifdef NOTIFICATIONS +SOURCES += src/notifications/notify.d src/notifications/dnotify.d +endif + all: onedrive onedrive.service onedrive.1 clean: diff --git a/README.md b/README.md index 2d838cb6..5c0d76c3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ * Support OneDrive for Business (part of Office 365) * Shared folders (OneDrive Personal) * SharePoint / Office 365 Shared Libraries (refer to README.Office365.md to configure) +* Notifications ### What's missing: * While local changes are uploaded right away, remote changes are delayed @@ -26,6 +27,10 @@ sudo apt install libcurl4-openssl-dev sudo apt install libsqlite3-dev curl -fsS https://dlang.org/install.sh | bash -s dmd ``` +For notifications the following is necessary: +``` +sudo apt install libnotify-dev +``` ### Dependencies: Ubuntu - i386 / i686 **Note:** Validated with `Linux ubuntu-i386-vm 4.13.0-36-generic #40~16.04.1-Ubuntu SMP Fri Feb 16 23:26:51 UTC 2018 i686 i686 i686 GNU/Linux` and DMD 2.081.1 @@ -35,6 +40,10 @@ sudo apt install libcurl4-openssl-dev sudo apt install libsqlite3-dev curl -fsS https://dlang.org/install.sh | bash -s dmd ``` +For notifications the following is necessary: +``` +sudo apt install libnotify-dev +``` ### Dependencies: Debian - i386 / i686 **Note:** Validated with `Linux debian-i386 4.9.0-7-686-pae #1 SMP Debian 4.9.110-1 (2018-07-05) i686 GNU/Linux` and LDC - the LLVM D compiler (1.8.0). @@ -56,6 +65,10 @@ wget http://ftp.us.debian.org/debian/pool/main/l/llvm-toolchain-5.0/libllvm5.0_5 wget http://ftp.us.debian.org/debian/pool/main/n/ncurses/libtinfo6_6.1+20180714-1_i386.deb sudo dpkg -i ./*.deb ``` +For notifications the following is necessary: +``` +sudo apt install libnotify-dev +``` ### Dependencies: Fedora < Version 18 / CentOS / RHEL ``` @@ -64,6 +77,10 @@ sudo yum install libcurl-devel sudo yum install sqlite-devel curl -fsS https://dlang.org/install.sh | bash -s dmd ``` +For notifications the following is necessary: +``` +sudo yum install libnotify-devel +``` ### Dependencies: Fedora > Version 18 ``` @@ -72,11 +89,19 @@ sudo dnf install libcurl-devel sudo dnf install sqlite-devel curl -fsS https://dlang.org/install.sh | bash -s dmd ``` +For notifications the following is necessary: +``` +sudo yum install libnotify-devel +``` ### Dependencies: Arch Linux ``` sudo pacman -S curl sqlite dmd ``` +For notifications the following is necessary: +``` +sudo pacman -S libnotify +``` ### Dependencies: Raspbian (ARMHF) ``` @@ -85,6 +110,10 @@ sudo apt-get install libsqlite3-dev wget https://github.com/ldc-developers/ldc/releases/download/v1.11.0/ldc2-1.11.0-linux-armhf.tar.xz tar -xvf ldc2-1.11.0-linux-armhf.tar.xz ``` +For notifications the following is necessary: +``` +sudo apt install libnotify-dev +``` ### Dependencies: Debian (ARM64) ``` @@ -93,6 +122,10 @@ sudo apt-get install libsqlite3-dev wget https://github.com/ldc-developers/ldc/releases/download/v1.11.0/ldc2-1.11.0-linux-aarch64.tar.xz tar -xvf ldc2-1.11.0-linux-aarch64.tar.xz ``` +For notifications the following is necessary: +``` +sudo apt install libnotify-dev +``` ### Dependencies: Gentoo ``` @@ -101,11 +134,20 @@ sudo layman -a dlang ``` Add ebuild from contrib/gentoo to a local overlay to use. +For notifications the following is necessary: +``` +sudo emerge x11-libs/libnotify +``` + ### Dependencies: OpenSuSE Leap 15.0 ``` sudo zypper addrepo --check --refresh --name "D" http://download.opensuse.org/repositories/devel:/languages:/D/openSUSE_Leap_15.0/devel:languages:D.repo sudo zypper install git libcurl-devel sqlite3-devel D:dmd D:libphobos2-0_81 D:phobos-devel D:phobos-devel-static ``` +For notifications the following is necessary: +``` +sudo zypper install libnotify-devel +``` ## Compilation & Installation ### Building using DMD Reference Compiler: @@ -126,6 +168,13 @@ make sudo make install ``` +### Build options ### +By passing `NOTIFICATIONS=1` to the `make` call, notifications via +libnotify are enabled. Necessary libraries are +`gmodule-2.0`, `glib-2.0`, and `notify`. If these libraries are +named differently on the build system, the make variable +`DFLAGSNOTIFICATIONS` can be adjusted. + ### Building using a different compiler (for example [LDC](https://wiki.dlang.org/LDC)): #### Debian - i386 / i686 ``` diff --git a/onedrive.1.in b/onedrive.1.in index 866b215f..67a6ada7 100644 --- a/onedrive.1.in +++ b/onedrive.1.in @@ -27,6 +27,9 @@ Debug OneDrive HTTPS communication. \fB\-d \-\-download\-only\fP Only download remote changes .TP +\fB\-\-disable\-notifications\fP +Do not use desktop notifications in monitor mode +.TP \fB\-\-disable\-upload\-validation\fP Disable upload validation when uploading to OneDrive .TP @@ -191,6 +194,15 @@ All logfiles will be in the format of \fB%username%.onedrive.log\fP, where \fB%username%\fP represents the user who ran the client. +.SH NOTIFICATIONS + +If OneDrive has been compiled with support for notifications, a running +\fBonedrive\fP in monitor mode will send notifications about +initialization and errors via libnotify to the dbus. + +Note that this does not work if \fBonedrive\fP is started as root +for a user via the \fBonedrive@\fP service. + .SH SEE ALSO Further examples and documentation is available in diff --git a/src/log.d b/src/log.d index a10b2096..cc2c8cf9 100644 --- a/src/log.d +++ b/src/log.d @@ -2,13 +2,19 @@ import std.stdio; import std.file; import std.datetime; import std.process; +import std.conv; import core.sys.posix.pwd, core.sys.posix.unistd, core.stdc.string : strlen; import std.algorithm : splitter; +version(Notifications) { + import dnotify; +} // enable verbose logging bool verbose; bool writeLogFile = false; +private bool doNotifications; + // shared string variable for username string username; string logFilePath; @@ -33,6 +39,11 @@ void init(string logDir) } } +void setNotifications(bool value) +{ + doNotifications = value; +} + void log(T...)(T args) { writeln(args); @@ -42,6 +53,12 @@ void log(T...)(T args) } } +void logAndNotify(T...)(T args) +{ + notify(args); + log(args); +} + void fileOnly(T...)(T args) { if(writeLogFile){ @@ -70,6 +87,32 @@ void error(T...)(T args) } } +void errorAndNotify(T...)(T args) +{ + notify(args); + error(args); +} + +void notify(T...)(T args) +{ + version(Notifications) { + if (doNotifications) { + string result; + foreach (index, arg; args) { + result ~= to!string(arg); + if (index != args.length - 1) + result ~= " "; + } + auto n = new Notification("OneDrive", result, "IGNORED"); + try { + n.show(); + } catch (Throwable e) { + vlog("Got exception from showing notification: ", e); + } + } + } +} + private void logfileWriteLine(T...)(T args) { // Write to log file @@ -105,4 +148,4 @@ private string getUserName() // Unknown user? return "unknown"; } -} \ No newline at end of file +} diff --git a/src/main.d b/src/main.d index 73533ad7..f3da1d70 100644 --- a/src/main.d +++ b/src/main.d @@ -90,6 +90,8 @@ int main(string[] args) bool disableUploadValidation = false; // SharePoint / Office 365 Shared Library name to query string o365SharedLibraryName; + // Do not use notifications in monitor mode + bool disableNotifications = false; try { auto opt = getopt( @@ -101,6 +103,7 @@ int main(string[] args) "create-directory", "Create a directory on OneDrive - no sync will be performed.", &createDirectory, "destination-directory", "Destination directory for renamed or move on OneDrive - no sync will be performed.", &destinationDirectory, "debug-https", "Debug OneDrive HTTPS communication.", &debugHttp, + "disable-notifications", "Do not use desktop notifications in monitor mode.", &disableNotifications, "download-only|d", "Only download remote changes", &downloadOnly, "disable-upload-validation", "Disable upload validation when uploading to OneDrive", &disableUploadValidation, "enable-logging", "Enable client activity to a separate log file", &enableLogFile, @@ -134,7 +137,7 @@ int main(string[] args) log.error("Try 'onedrive -h' for more information"); return EXIT_FAILURE; } - + // disable buffering on stdout stdout.setvbuf(0, _IONBF); @@ -158,6 +161,9 @@ int main(string[] args) log.vlog("Using logfile dir: ", logDir); log.init(logDir); } + + // Configure whether notifications are used + log.setNotifications(monitor && !disableNotifications); // command line parameters override the config if (syncDirName) cfg.setValue("sync_dir", syncDirName.expandTilde().absolutePath()); @@ -166,7 +172,7 @@ int main(string[] args) // upgrades if (exists(configDirName ~ "/items.db")) { remove(configDirName ~ "/items.db"); - log.log("Database schema changed, resync needed"); + log.logAndNotify("Database schema changed, resync needed"); resync = true; } @@ -257,7 +263,7 @@ int main(string[] args) selectiveSync.setMask(cfg.getValue("skip_file")); // Initialise the sync engine - log.log("Initializing the Synchronization Engine ..."); + log.logAndNotify("Initializing the Synchronization Engine ..."); auto sync = new SyncEngine(cfg, onedrive, itemdb, selectiveSync); try { @@ -283,7 +289,7 @@ int main(string[] args) if (checkMount) { // we were asked to check the mounts if (exists(syncDir ~ "/.nosync")) { - log.log("\nERROR: .nosync file found. Aborting synchronization process to safeguard data."); + log.logAndNotify("ERROR: .nosync file found. Aborting synchronization process to safeguard data."); onedrive.http.shutdown(); return EXIT_FAILURE; } @@ -324,7 +330,7 @@ int main(string[] args) // Does the directory we want to sync actually exist? if (!exists(singleDirectory)){ // the requested directory does not exist .. - log.log("The requested local directory does not exist. Please check ~/OneDrive/ for requested path"); + log.logAndNotify("ERROR: The requested local directory does not exist. Please check ~/OneDrive/ for requested path"); onedrive.http.shutdown(); return EXIT_FAILURE; } @@ -336,7 +342,7 @@ int main(string[] args) } if (monitor) { - log.log("Initializing monitor ..."); + log.logAndNotify("Initializing monitor ..."); log.log("OneDrive monitor interval (seconds): ", to!long(cfg.getValue("monitor_interval"))); Monitor m = new Monitor(selectiveSync); m.onDirCreated = delegate(string path) { @@ -344,7 +350,7 @@ int main(string[] args) try { sync.scanForDifferences(path); } catch(Exception e) { - log.log(e.msg); + log.logAndNotify(e.msg); } }; m.onFileChanged = delegate(string path) { @@ -352,7 +358,7 @@ int main(string[] args) try { sync.scanForDifferences(path); } catch(Exception e) { - log.log(e.msg); + log.logAndNotify(e.msg); } }; m.onDelete = delegate(string path) { @@ -360,7 +366,7 @@ int main(string[] args) try { sync.deleteByPath(path); } catch(Exception e) { - log.log(e.msg); + log.logAndNotify(e.msg); } }; m.onMove = delegate(string from, string to) { @@ -368,7 +374,7 @@ int main(string[] args) try { sync.uploadMoveItem(from, to); } catch(Exception e) { - log.log(e.msg); + log.logAndNotify(e.msg); } }; // initialise the monitor class @@ -395,6 +401,7 @@ int main(string[] args) // TODO better check of type of exception from Curl // could be either timeout of operation of connection error // No network connection to OneDrive Service + // Don't overload notifications log.log("No network connection to Microsoft OneDrive Service, skipping sync"); } // performSync complete, set lastCheckTime to current time @@ -502,7 +509,8 @@ void performSync(SyncEngine sync, string singleDirectory, bool downloadOnly, boo count = -1; } catch (Exception e) { if (++count == 3) throw e; - else log.log(e.msg); + else log.logAndNotify(e.msg); } } while (count != -1); } + diff --git a/src/notifications/README b/src/notifications/README new file mode 100644 index 00000000..3ee1f10a --- /dev/null +++ b/src/notifications/README @@ -0,0 +1,10 @@ +The files in this directory have been obtained form the following places: + +dnotify.d + https://github.com/Dav1dde/dnotify/blob/master/dnotify.d + License: Creative Commons Zro 1.0 Universal + see https://github.com/Dav1dde/dnotify/blob/master/LICENSE + +notify.d + https://github.com/D-Programming-Deimos/libnotify/blob/master/deimos/notify/notify.d + License: GPL 2.1 or upwards, see file diff --git a/src/notifications/dnotify.d b/src/notifications/dnotify.d new file mode 100644 index 00000000..1a2fda23 --- /dev/null +++ b/src/notifications/dnotify.d @@ -0,0 +1,309 @@ +module dnotify; + +private { + import std.string : toStringz; + import std.conv : to; + import std.traits : isPointer, isArray; + import std.variant : Variant; + import std.array : appender; + + import deimos.notify.notify; +} + +public import deimos.notify.notify : NOTIFY_EXPIRES_DEFAULT, NOTIFY_EXPIRES_NEVER, + NotifyUrgency; + + +version(NoPragma) { +} else { + pragma(lib, "notify"); + pragma(lib, "gmodule"); + pragma(lib, "glib-2.0"); +} + +extern (C) { + private void g_free(void* mem); + private void g_list_free(GList* glist); +} + +version(NoGdk) { +} else { + version(NoPragma) { + } else { + pragma(lib, "gdk_pixbuf"); + } + + private: + extern (C) { + GdkPixbuf* gdk_pixbuf_new_from_file(const(char)* filename, GError **error); + } +} + +class NotificationError : Exception { + string message; + GError* gerror; + + this(GError* gerror) { + this.message = to!(string)(gerror.message); + this.gerror = gerror; + + super(this.message); + } + + this(string message) { + this.message = message; + + super(message); + } +} + + +void init(in char[] name) { + notify_init(name.toStringz()); +} + +alias notify_is_initted is_initted; +alias notify_uninit uninit; + +static this() { + init(__FILE__); +} + +static ~this() { + uninit(); +} + +string get_app_name() { + return to!(string)(notify_get_app_name()); +} + +void set_app_name(in char[] app_name) { + notify_set_app_name(app_name.toStringz()); +} + +string[] get_server_caps() { + auto result = appender!(string[])(); + + GList* list = notify_get_server_caps(); + if(list !is null) { + for(GList* c = list; c !is null; c = c.next) { + result.put(to!(string)(cast(char*)c.data)); + g_free(c.data); + } + + g_list_free(list); + } + + return result.data; +} + +struct ServerInfo { + string name; + string vendor; + string version_; + string spec_version; +} + +ServerInfo get_server_info() { + char* name; + char* vendor; + char* version_; + char* spec_version; + notify_get_server_info(&name, &vendor, &version_, &spec_version); + + scope(exit) { + g_free(name); + g_free(vendor); + g_free(version_); + g_free(spec_version); + } + + return ServerInfo(to!string(name), to!string(vendor), to!string(version_), to!string(spec_version)); +} + + +struct Action { + const(char[]) id; + const(char[]) label; + NotifyActionCallback callback; + void* user_ptr; +} + + +class Notification { + NotifyNotification* notify_notification; + + const(char)[] summary; + const(char)[] body_; + const(char)[] icon; + + bool closed = true; + + private int _timeout = NOTIFY_EXPIRES_DEFAULT; + const(char)[] _category; + NotifyUrgency _urgency; + GdkPixbuf* _image; + Variant[const(char)[]] _hints; + const(char)[] _app_name; + Action[] _actions; + + this(in char[] summary, in char[] body_, in char[] icon="") + in { assert(is_initted(), "call dnotify.init() before using Notification"); } + body { + this.summary = summary; + this.body_ = body_; + this.icon = icon; + notify_notification = notify_notification_new(summary.toStringz(), body_.toStringz(), icon.toStringz()); + } + + bool update(in char[] summary, in char[] body_, in char[] icon="") { + this.summary = summary; + this.body_ = body_; + this.icon = icon; + return notify_notification_update(notify_notification, summary.toStringz(), body_.toStringz(), icon.toStringz()); + } + + void show() { + GError* ge; + + if(!notify_notification_show(notify_notification, &ge)) { + throw new NotificationError(ge); + } + } + + @property int timeout() { return _timeout; } + @property void timeout(int timeout) { + this._timeout = timeout; + notify_notification_set_timeout(notify_notification, timeout); + } + + @property const(char[]) category() { return _category; } + @property void category(in char[] category) { + this._category = category; + notify_notification_set_category(notify_notification, category.toStringz()); + } + + @property NotifyUrgency urgency() { return _urgency; } + @property void urgency(NotifyUrgency urgency) { + this._urgency = urgency; + notify_notification_set_urgency(notify_notification, urgency); + } + + + void set_image(GdkPixbuf* pixbuf) { + notify_notification_set_image_from_pixbuf(notify_notification, pixbuf); + //_image = pixbuf; + } + + version(NoGdk) { + } else { + void set_image(in char[] filename) { + GError* ge; + // TODO: free pixbuf + GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file(filename.toStringz(), &ge); + + if(pixbuf is null) { + if(ge is null) { + throw new NotificationError("Unable to load file: " ~ filename.idup); + } else { + throw new NotificationError(ge); + } + } + assert(notify_notification !is null); + notify_notification_set_image_from_pixbuf(notify_notification, pixbuf); // TODO: fix segfault + //_image = pixbuf; + } + } + + @property GdkPixbuf* image() { return _image; } + + // using deprecated set_hint_* functions (GVariant is an opaque structure, which needs the glib) + void set_hint(T)(in char[] key, T value) { + static if(is(T == int)) { + notify_notification_set_hint_int32(notify_notification, key, value); + } else static if(is(T == uint)) { + notify_notification_set_hint_uint32(notify_notification, key, value); + } else static if(is(T == double)) { + notify_notification_set_hint_double(notify_notification, key, value); + } else static if(is(T : const(char)[])) { + notify_notification_set_hint_string(notify_notification, key, value.toStringz()); + } else static if(is(T == ubyte)) { + notify_notification_set_hint_byte(notify_notification, key, value); + } else static if(is(T == ubyte[])) { + notify_notification_set_hint_byte_array(notify_notification, key, value.ptr, value.length); + } else { + static assert(false, "unsupported value for Notification.set_hint"); + } + + _hints[key] = Variant(value); + } + + // unset hint? + + Variant get_hint(in char[] key) { + return _hints[key]; + } + + @property const(char)[] app_name() { return _app_name; } + @property void app_name(in char[] name) { + this._app_name = app_name; + notify_notification_set_app_name(notify_notification, app_name.toStringz()); + } + + void add_action(T)(in char[] action, in char[] label, NotifyActionCallback callback, T user_data) { + static if(isPointer!T) { + void* user_ptr = cast(void*)user_data; + } else static if(isArray!T) { + void* user_ptr = cast(void*)user_data.ptr; + } else { + void* user_ptr = cast(void*)&user_data; + } + + notify_notification_add_action(notify_notification, action.toStringz(), label.toStringz(), + callback, user_ptr, null); + + _actions ~= Action(action, label, callback, user_ptr); + } + + void add_action()(Action action) { + notify_notification_add_action(notify_notification, action.id.toStringz(), action.label.toStringz(), + action.callback, action.user_ptr, null); + + _actions ~= action; + } + + @property Action[] actions() { return _actions; } + + void clear_actions() { + notify_notification_clear_actions(notify_notification); + } + + void close() { + GError* ge; + + if(!notify_notification_close(notify_notification, &ge)) { + throw new NotificationError(ge); + } + } + + @property int closed_reason() { + return notify_notification_get_closed_reason(notify_notification); + } +} + + +version(TestMain) { + import std.stdio; + + void main() { + writeln(get_app_name()); + set_app_name("bla"); + writeln(get_app_name()); + writeln(get_server_caps()); + writeln(get_server_info()); + + auto n = new Notification("foo", "bar", "notification-message-im"); + n.timeout = 3; + n.show(); + } +} \ No newline at end of file diff --git a/src/notifications/notify.d b/src/notifications/notify.d new file mode 100644 index 00000000..c549e397 --- /dev/null +++ b/src/notifications/notify.d @@ -0,0 +1,195 @@ +/** + * Copyright (C) 2004-2006 Christian Hammond + * Copyright (C) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +module deimos.notify.notify; + + +enum NOTIFY_VERSION_MAJOR = 0; +enum NOTIFY_VERSION_MINOR = 7; +enum NOTIFY_VERSION_MICRO = 5; + +template NOTIFY_CHECK_VERSION(int major, int minor, int micro) { + enum NOTIFY_CHECK_VERSION = ((NOTIFY_VERSION_MAJOR > major) || + (NOTIFY_VERSION_MAJOR == major && NOTIFY_VERSION_MINOR > minor) || + (NOTIFY_VERSION_MAJOR == major && NOTIFY_VERSION_MINOR == minor && + NOTIFY_VERSION_MICRO >= micro)); +} + + +alias ulong GType; +alias void function(void*) GFreeFunc; + +struct GError { + uint domain; + int code; + char* message; +} + +struct GList { + void* data; + GList* next; + GList* prev; +} + +// dummies +struct GdkPixbuf {} +struct GObject {} +struct GObjectClass {} +struct GVariant {} + +GType notify_urgency_get_type(); + +/** + * NOTIFY_EXPIRES_DEFAULT: + * + * The default expiration time on a notification. + */ +enum NOTIFY_EXPIRES_DEFAULT = -1; + +/** + * NOTIFY_EXPIRES_NEVER: + * + * The notification never expires. It stays open until closed by the calling API + * or the user. + */ +enum NOTIFY_EXPIRES_NEVER = 0; + +// #define NOTIFY_TYPE_NOTIFICATION (notify_notification_get_type ()) +// #define NOTIFY_NOTIFICATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NOTIFY_TYPE_NOTIFICATION, NotifyNotification)) +// #define NOTIFY_NOTIFICATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NOTIFY_TYPE_NOTIFICATION, NotifyNotificationClass)) +// #define NOTIFY_IS_NOTIFICATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NOTIFY_TYPE_NOTIFICATION)) +// #define NOTIFY_IS_NOTIFICATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NOTIFY_TYPE_NOTIFICATION)) +// #define NOTIFY_NOTIFICATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NOTIFY_TYPE_NOTIFICATION, NotifyNotificationClass)) + +extern (C) { + struct NotifyNotificationPrivate; + + struct NotifyNotification { + /*< private >*/ + GObject parent_object; + + NotifyNotificationPrivate *priv; + } + + struct NotifyNotificationClass { + GObjectClass parent_class; + + /* Signals */ + void function(NotifyNotification *notification) closed; + } + + + /** + * NotifyUrgency: + * @NOTIFY_URGENCY_LOW: Low urgency. Used for unimportant notifications. + * @NOTIFY_URGENCY_NORMAL: Normal urgency. Used for most standard notifications. + * @NOTIFY_URGENCY_CRITICAL: Critical urgency. Used for very important notifications. + * + * The urgency level of the notification. + */ + enum NotifyUrgency { + NOTIFY_URGENCY_LOW, + NOTIFY_URGENCY_NORMAL, + NOTIFY_URGENCY_CRITICAL, + + } + + /** + * NotifyActionCallback: + * @notification: + * @action: + * @user_data: + * + * An action callback function. + */ + alias void function(NotifyNotification* notification, char* action, void* user_data) NotifyActionCallback; + + + GType notify_notification_get_type(); + + NotifyNotification* notify_notification_new(const(char)* summary, const(char)* body_, const(char)* icon); + + bool notify_notification_update(NotifyNotification* notification, const(char)* summary, const(char)* body_, const(char)* icon); + + bool notify_notification_show(NotifyNotification* notification, GError** error); + + void notify_notification_set_timeout(NotifyNotification* notification, int timeout); + + void notify_notification_set_category(NotifyNotification* notification, const(char)* category); + + void notify_notification_set_urgency(NotifyNotification* notification, NotifyUrgency urgency); + + void notify_notification_set_image_from_pixbuf(NotifyNotification* notification, GdkPixbuf* pixbuf); + + void notify_notification_set_icon_from_pixbuf(NotifyNotification* notification, GdkPixbuf* icon); + + void notify_notification_set_hint_int32(NotifyNotification* notification, const(char)* key, int value); + void notify_notification_set_hint_uint32(NotifyNotification* notification, const(char)* key, uint value); + + void notify_notification_set_hint_double(NotifyNotification* notification, const(char)* key, double value); + + void notify_notification_set_hint_string(NotifyNotification* notification, const(char)* key, const(char)* value); + + void notify_notification_set_hint_byte(NotifyNotification* notification, const(char)* key, ubyte value); + + void notify_notification_set_hint_byte_array(NotifyNotification* notification, const(char)* key, const(ubyte)* value, ulong len); + + void notify_notification_set_hint(NotifyNotification* notification, const(char)* key, GVariant* value); + + void notify_notification_set_app_name(NotifyNotification* notification, const(char)* app_name); + + void notify_notification_clear_hints(NotifyNotification* notification); + + void notify_notification_add_action(NotifyNotification* notification, const(char)* action, const(char)* label, + NotifyActionCallback callback, void* user_data, GFreeFunc free_func); + + void notify_notification_clear_actions(NotifyNotification* notification); + bool notify_notification_close(NotifyNotification* notification, GError** error); + + int notify_notification_get_closed_reason(const NotifyNotification* notification); + + + + bool notify_init(const(char)* app_name); + void notify_uninit(); + bool notify_is_initted(); + + const(char)* notify_get_app_name(); + void notify_set_app_name(const(char)* app_name); + + GList *notify_get_server_caps(); + + bool notify_get_server_info(char** ret_name, char** ret_vendor, char** ret_version, char** ret_spec_version); +} + +version(MainTest) { + import std.string; + + void main() { + + notify_init("test".toStringz()); + + auto n = notify_notification_new("summary".toStringz(), "body".toStringz(), "none".toStringz()); + GError* ge; + notify_notification_show(n, &ge); + + scope(success) notify_uninit(); + } +} diff --git a/src/onedrive.d b/src/onedrive.d index 5ae49057..b41fd0be 100644 --- a/src/onedrive.d +++ b/src/onedrive.d @@ -545,7 +545,8 @@ final class OneDriveApi http.perform(); } catch (CurlException e) { // Potentially Timeout was reached on handle error - log.error("\nAccess to the Microsoft OneDrive service timed out - Internet connectivity issue?\n"); + // we issue warning/error in the catch routines so no need to warn here + // log.error("\nAccess to the Microsoft OneDrive service timed out - Internet connectivity issue?\n"); throw e; }