diff --git a/CanvasSync/GUI/canvas_logo.png b/CanvasSync/GUI/canvas_logo.png
new file mode 100644
index 0000000..a0a64cf
Binary files /dev/null and b/CanvasSync/GUI/canvas_logo.png differ
diff --git a/CanvasSync/GUI/macos_statusbar.py b/CanvasSync/GUI/macos_statusbar.py
new file mode 100755
index 0000000..3240bb0
--- /dev/null
+++ b/CanvasSync/GUI/macos_statusbar.py
@@ -0,0 +1,143 @@
+"""
+This script is the actual MacOS Statusbar for CanvasSync.
+Execute this script to run the CanvasSync Statusbar.
+Run startup_installer.py to add the CanvasSync Statsubar to the system StartUp.
+"""
+from rumps import *
+import pathlib
+import sys
+import subprocess
+import AppKit
+import configparser
+import os.path
+import datetime
+import keyring
+import pymsgbox
+
+
+class config_file:
+ def __init__(self, filepath):
+ self.filepath = filepath
+ if not os.path.exists(self.filepath):
+ open(self.filepath, 'a').close()
+ self.config = configparser.ConfigParser()
+ self.config.read(filepath)
+
+ def read(self, section, parameter):
+ if not self.config.has_option(section, parameter):
+ return None
+ else:
+ return self.config[section][parameter]
+
+ def write(self, section, parameter, value):
+ if not self.config.has_section(section):
+ self.config.add_section(section)
+ self.config.set(section, parameter, value)
+ with open(self.filepath, 'w') as configfile:
+ self.config.write(configfile)
+
+
+from tkinter import *
+
+def pop_up():
+ app = Tk()
+ app.title("CanvasSync Password")
+ label = Label(app, text="Please enter the CanvasSync Password:")
+ label.grid()
+ pwd = StringVar()
+ e1 = Entry(app, width=40, textvariable=pwd)
+ btn = Button(app, text="Ok", command=app.destroy)
+ btn.grid(row=2)
+ e1.grid(row=1)
+
+ app.mainloop()
+ return pwd.get()
+
+def savepassword():
+ response = str(pop_up())
+ keyring.set_password('CanvasSync', 'xkcd', response)
+
+
+def changeSchedule(ToNew):
+ """
+ This function changes the scheduler settings file so that the selected interval in the settings file is saved there.
+ Arguments:
+ ToNew (str): The interval changed to/the new interval.
+ """
+ config.write('Settings', 'interval', ToNew)
+
+
+def adjust_interval(self):
+ # Change tick-mark in Statusbar on change
+ app.menu["Automatic sync"]['hourly'].state = 0
+ app.menu["Automatic sync"]['every 6 hours'].state = 0
+ app.menu["Automatic sync"]['daily'].state = 0
+ app.menu["Automatic sync"]['Do not sync'].state = 0
+ self.state = 1
+ # Save setting in Scheduler-Settings-File
+ if self.title == 'hourly':
+ changeSchedule('hourly')
+ elif self.title == 'every 6 hours':
+ changeSchedule('every 6 hours')
+ elif self.title == 'daily':
+ changeSchedule('daily')
+ elif self.title == 'Do not sync':
+ changeSchedule('Do not sync')
+
+
+@clicked("Sync now")
+def prefs(_):
+ # Manual Sync now
+ rumps.notification("Canvas Sync", "Syncing...", "The Canvas sync was started manually.")
+ # Add folder of canvas module to search directory
+ path = str(pathlib.Path(__file__).parent.parent.parent) + "/bin/"
+ sys.path.insert(0, path)
+ # Actual syncing
+ import canvas
+ settings = canvas.Settings()
+ password = keyring.get_password('CanvasSync', 'xkcd')
+ canvas.do_sync(settings, password)
+ rumps.notification("Canvas Sync", "Finished Synchronisation", "The Manual Canvas-Sync-task was finished.")
+ config.write('Last run', 'time', str(datetime.datetime.now().strftime("%d.%m.%Y %H:%M:%S")))
+
+
+@clicked("Preferences")
+def sayhi(_):
+ # Will be added later on
+ rumps.alert("Not yet available! In next version...")
+
+
+hourly = MenuItem('hourly', callback=adjust_interval)
+every6 = MenuItem('every 6 hours', callback=adjust_interval)
+daily = MenuItem('daily', callback=adjust_interval)
+no_sync = MenuItem('Do not sync', callback=adjust_interval)
+
+if __name__ == "__main__":
+ if keyring.get_password('CanvasSync', 'xkcd') is None:
+ savepassword()
+
+ config = config_file(str(pathlib.Path(__file__).parent.parent) + "/scheduler/scheduler.ini")
+ interval = config.read('Settings', 'interval')
+
+ # Do not appear in Mac Dock
+ info = AppKit.NSBundle.mainBundle().infoDictionary()
+ info["LSBackgroundOnly"] = "1"
+
+ # Set Statusbar Properties
+ app = App('Canvas Sync', icon='canvas_logo.png')
+ app.menu = [("Sync now"), ("Automatic sync", [hourly, every6, daily, None, no_sync]), "Preferences"]
+ path = str(pathlib.Path(__file__).parent.parent) + '/scheduler/scheduler.py'
+
+ # Run Scheduler as independent subprocess
+ #subprocess.run(["python", path])
+ from subprocess import Popen, PIPE
+ p = subprocess.Popen([sys.executable, path],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+
+ # Get Current Sync-Setting from file and select the selected in Statusbar
+ if not interval == None:
+ app.menu["Automatic sync"][interval].state = 1
+
+ # Run Statusbar
+ app.run()
diff --git a/CanvasSync/GUI/startup_installer.py b/CanvasSync/GUI/startup_installer.py
new file mode 100644
index 0000000..f19a1ab
--- /dev/null
+++ b/CanvasSync/GUI/startup_installer.py
@@ -0,0 +1,98 @@
+"""
+This script is to add/remove the CanvasSync Statusbar in MacOS to the LaunchAgent so that is automatically starts at startup.
+Run this file or the function ActivateStartUpStatusbar() to add it to startup.
+Run the function DeactivateStartUpStatusbar() to remove it from startup.
+"""
+
+import pathlib
+import os
+import sys
+import platform
+
+def CreateStartupFile():
+ with open(str(pathlib.Path(__file__).parent) + '/startup_template.plist', 'r') as file:
+ script = str(file.read())
+
+ script = script.replace('[@replaceCanvasPath]', str(pathlib.Path(__file__).parent))
+ script = script.replace('[@replacePythonPath]', str(sys.executable))
+
+ path_s = str(os.path.expanduser('~')) + "/Library/LaunchAgents/com.CanvasSync.Statusbar.plist"
+ with open(path_s, 'w') as file:
+ file.write(script)
+
+def AddWinTaskScheduler(wkdir, command, enabled):
+ import datetime
+ import win32com.client
+
+ scheduler = win32com.client.Dispatch('Schedule.Service')
+ scheduler.Connect()
+ root_folder = scheduler.GetFolder('\\')
+ task_def = scheduler.NewTask(0)
+
+ # Create trigger
+ #end_time = datetime.datetime.now()
+ TASK_TRIGGER_TIME = 9
+ trigger = task_def.Triggers.Create(TASK_TRIGGER_TIME)
+ #trigger.EndBoundary = end_time.isoformat()
+ trigger.ExecutionTimeLimit = "PT5M"
+ trigger.Id = "LogonTriggerId"
+ import win32api
+ user = win32api.GetUserName()
+ trigger.UserId = user
+ trigger.ExecutionTimeLimit = "P0M2DT0H0M"
+ trigger.enabled = enabled
+
+ # Create action
+ TASK_ACTION_EXEC = 0
+ action = task_def.Actions.Create(TASK_ACTION_EXEC)
+ action.ID = 'DO NOTHING'
+ action.Path = 'cmd.exe'
+ action.Arguments = '/c "' + str(command) + '"'
+ action.WorkingDirectory = str(wkdir)
+
+ # Set parameters
+ task_def.RegistrationInfo.Description = 'Run CanvasSync at System Startup'
+ task_def.Settings.Enabled = True
+ task_def.Settings.StopIfGoingOnBatteries = False
+ task_def.Settings.DisallowStartIfOnBatteries = False
+ task_def.Settings.Hidden = True
+
+ # Register task
+ # If task already exists, it will be updated
+ TASK_CREATE_OR_UPDATE = 6
+ TASK_LOGON_NONE = 0
+ root_folder.RegisterTaskDefinition(
+ 'CanvasSync', # Task name
+ task_def,
+ TASK_CREATE_OR_UPDATE,
+ '', # No user
+ '', # No password
+ TASK_LOGON_NONE)
+
+
+
+def ActivateStartUpStatusbar():
+ # ToDo: In Windows subpress feedback e.g. when asking if run this package that is not in PATH.
+ if platform.system() == 'Windows':
+ path = str(pathlib.Path(__file__).parent)
+ AddWinTaskScheduler(path, r'python .\windows_systemtray.py -y', True)
+
+ else: #MacOS
+ CreateStartupFile()
+ load_command = "launchctl load " + str(os.path.expanduser('~')) + "/Library/LaunchAgents/com.CanvasSync.Statusbar.plist"
+ os.system(load_command)
+
+def DeactivateStartUpStatusbar():
+ if platform.system() == 'Windows':
+ path = str(pathlib.Path(__file__).parent)
+ AddWinTaskScheduler(path, r'python .\windows_systemtray.py -y', False)
+ else: # MacOS
+ unload_command = "launchctl unload " + str(os.path.expanduser('~')) + "/Library/LaunchAgents/com.CanvasSync.Statusbar.plist"
+ stop_command = "launchctl stop " + str(os.path.expanduser('~')) + "/Library/LaunchAgents/com.CanvasSync.Statusbar.plist"
+ os.system(unload_command)
+ os.system(stop_command)
+
+
+# If main module
+if __name__ == u"__main__":
+ ActivateStartUpStatusbar()
diff --git a/CanvasSync/GUI/startup_template.plist b/CanvasSync/GUI/startup_template.plist
new file mode 100644
index 0000000..c20a35b
--- /dev/null
+++ b/CanvasSync/GUI/startup_template.plist
@@ -0,0 +1,19 @@
+
+
+
+
+ Label
+ com.CanvasSync.Statusbar
+ ProgramArguments
+
+ [@replacePythonPath]
+ [@replaceCanvasPath]/macos_statusbar.py
+
+ StandardErrorPath
+ [@replaceCanvasPath]/statusbar.err
+ StandardOutPath
+ [@replaceCanvasPath]/statusbar.out
+ KeepAlive
+
+
+
diff --git a/CanvasSync/GUI/windows_systemtray.py b/CanvasSync/GUI/windows_systemtray.py
new file mode 100644
index 0000000..a726add
--- /dev/null
+++ b/CanvasSync/GUI/windows_systemtray.py
@@ -0,0 +1,131 @@
+from PIL import Image
+import pathlib, configparser, os, sys, datetime, keyring, subprocess
+from pystray import Icon as icon, Menu as menu, MenuItem as item
+import pystray
+import PIL
+
+class config_file:
+ def __init__(self, filepath):
+ self.filepath = filepath
+ if not os.path.exists(self.filepath):
+ open(self.filepath, 'a').close()
+ self.config = configparser.ConfigParser()
+ self.config.read(filepath)
+
+ def read(self, section, parameter):
+ if not self.config.has_option(section, parameter):
+ return None
+ else:
+ return self.config[section][parameter]
+
+ def write(self, section, parameter, value):
+ if not self.config.has_section(section):
+ self.config.add_section(section)
+ self.config.set(section, parameter, value)
+ with open(self.filepath, 'w') as configfile:
+ self.config.write(configfile)
+
+
+def set_state(v):
+ def inner(icon, item):
+ global state
+ state = v
+ config.write('Settings', 'interval', state_dict[v])
+ return inner
+
+from tkinter import *
+def get_state(v):
+ def inner(item):
+ #return state == v
+ return config.read('Settings', 'interval') == state_dict[v]
+ return inner
+
+
+def pop_up():
+ app = Tk()
+ app.title("CanvasSync Password")
+ label = Label(app, text="Please enter the CanvasSync Password:")
+ label.grid()
+ pwd = StringVar()
+ e1 = Entry(app, width=40, textvariable=pwd)
+ btn = Button(app, text="Ok", command=app.destroy)
+ btn.grid(row=2)
+ e1.grid(row=1)
+
+ app.mainloop()
+ return pwd.get()
+
+
+def savepassword():
+ response = str(pop_up())
+ keyring.set_password('CanvasSync', 'xkcd', response)
+
+def runSync():
+ print('sync')
+
+ # Import Canvas Module
+ path = str(pathlib.Path(__file__).parent.parent.parent) + "/bin/"
+ sys.path.insert(0, path)
+ import canvas
+
+ settings = canvas.Settings()
+ password = keyring.get_password('CanvasSync', 'xkcd')
+ canvas.do_sync(settings, password)
+ config.write('Last run', 'time', str(datetime.datetime.now().strftime("%d.%m.%Y %H:%M:%S")))
+
+def preferences():
+ pass
+
+def quit_icon():
+ #ToDo: Quit App does not work at the moment.
+ icon.stop()
+
+dir_path = os.path.dirname(os.path.realpath(__file__))
+
+if __name__ == '__main__':
+ image = PIL.Image.open('canvas_logo.png')
+
+ state = 0
+ state_dict = {
+ 1: 'hourly',
+ 2: 'every 6 hours',
+ 3: 'daily',
+ 4: 'Do not sync'
+ }
+
+ if keyring.get_password('CanvasSync', 'xkcd') is None:
+ savepassword()
+
+ # Run Scheduler as independent subprocess
+ path = str(os.path.abspath(os.path.join(os.path.join(os.path.abspath(__file__), '..'), '..'))) + '\\scheduler\\scheduler.py'
+ subprocess.Popen(["python", path])
+
+ config = config_file(str(os.path.abspath(os.path.join(os.path.join(os.path.abspath(__file__), '..'), '..'))) + "\\scheduler\\scheduler.ini")
+ icon('test', image, menu=menu(
+ item('Sync now', runSync),
+ item(
+ 'Automatic sync',
+ menu(
+ item(
+ 'hourly',
+ set_state(1),
+ checked=get_state(1),
+ radio=True),
+ item(
+ 'every 6 hours',
+ set_state(2),
+ checked=get_state(2),
+ radio=True),
+ item(
+ 'daily',
+ set_state(3),
+ checked=get_state(3),
+ radio=True),
+ item(
+ 'Do not sync',
+ set_state(4),
+ checked=get_state(4),
+ radio=True))),
+ item('Preferences', preferences),
+ item('Quit now', quit_icon)
+ )).run()
\ No newline at end of file
diff --git a/CanvasSync/scheduler/scheduler.py b/CanvasSync/scheduler/scheduler.py
new file mode 100644
index 0000000..7f5db0b
--- /dev/null
+++ b/CanvasSync/scheduler/scheduler.py
@@ -0,0 +1,91 @@
+"""
+This Script is the Scheduler for the automatic Canvas Sync. It constanly runs and checks every full hour if it needs to sync.
+It checks it every hour because the user could have changed the settings inbetween the last and the current run.
+"""
+import datetime, time
+import pathlib
+import sys
+import configparser
+import os.path
+import keyring
+import platform
+#import pymsgbox
+
+class config_file:
+ def __init__(self, filepath):
+ self.filepath = filepath
+ if not os.path.exists(self.filepath):
+ open(self.filepath, 'a').close()
+ self.config = configparser.ConfigParser()
+ self.config.read(filepath)
+
+ def read(self, section, parameter):
+ if not self.config.has_option(section, parameter):
+ return None
+ else:
+ return self.config[section][parameter]
+
+ def write(self, section, parameter, value):
+ if not self.config.has_section(section):
+ self.config.add_section(section)
+ self.config.set(section, parameter, value)
+ with open(self.filepath, 'w') as configfile:
+ self.config.write(configfile)
+
+
+def runSync():
+ print('sync')
+ if keyring.get_password('CanvasSync', 'xkcd') == None:
+ raise Exception("Error: Password for CanvasSync not saved in Keychain")
+ settings = canvas.Settings()
+ password = keyring.get_password('CanvasSync', 'xkcd')
+ canvas.do_sync(settings, password)
+ config.write('Last run', 'time', str(datetime.datetime.now().strftime("%d.%m.%Y %H:%M:%S")))
+
+
+if __name__ == '__main__':
+ # Import Canvas Module
+ path = str(pathlib.Path(__file__).parent.parent.parent) + "/bin/"
+ sys.path.insert(0, path)
+ # Set workind dir for windows
+ # ToDo: Check if still works with MacOS
+ if platform.system() == 'Windows':
+ os.chdir(path)
+ import canvas
+
+ # The Check if has to sync
+ dailysync = False
+ while True:
+ # read Config
+ config = config_file(str(pathlib.Path(__file__).parent) + "/scheduler.ini")
+ interval = config.read('Settings', 'interval')
+ print('interval ' + str(interval))
+ last_sync = config.read('Last run', 'time')
+ if last_sync == None:
+ last_sync = '01.01.2000 01:01:01'
+ last_sync = datetime.datetime.strptime(last_sync, "%d.%m.%Y %H:%M:%S")
+ print('last sync ' + str(last_sync.strftime("%d.%m.%Y %H:%M:%S")))
+ current_time = datetime.datetime.now()
+ print('current time ' + str(current_time.strftime("%d.%m.%Y %H:%M:%S")))
+ if interval == 'daily':
+ next_sync = last_sync + datetime.timedelta(days=1)
+ elif interval == 'Do not sync':
+ #do nothing
+ next_sync = last_sync + datetime.timedelta(hours=1)
+ pass
+ elif interval == 'hourly':
+ next_sync = last_sync + datetime.timedelta(hours=1)
+ elif interval == 'every 6 hours':
+ next_sync = last_sync + datetime.timedelta(hours=6)
+ else:
+ next_sync = last_sync + datetime.timedelta(minutes=10)
+ if next_sync < current_time and interval != 'Do not sync':
+ runSync()
+ else:
+ print('next sync ' + str(next_sync.strftime("%d.%m.%Y %H:%M:%S")))
+ # Calc time till next full hour and wait till then
+ waitseconds = (next_sync - current_time).total_seconds()
+ if waitseconds > 3600:
+ waitseconds = 3600
+ print('waiting ' + str(waitseconds) + ' seconds')
+ time.sleep(waitseconds)
\ No newline at end of file
diff --git a/README.md b/README.md
index 3c4e630..8a34d86 100755
--- a/README.md
+++ b/README.md
@@ -84,6 +84,19 @@ The authentication token is stored in an local file encrypted using a private pa
specify the password whenever CanvasSync is launched to synchronize at a later time. Passwords and/or auth tokens are
cannot and will not be shared with third parties.
+Statusbar
+----------
+For MacOS CanvasSync has also a statusbar, for Windows a icon at the taskbar. From here you can initalise a Synchronisation manually or setup an automatic sync.
+
+
+
+To run the Statusbar simply execute ```/GUI/macos_statusbar.py``` or ```/GUI/windows_systemtray.py```. You can also add it to the system startup by executing ```/GUI/startup_installer.py```. This script also contains a function to remove the statusbar from the system startup again.
+
+Still ToDo:
+- quiting does not work
+- preferences GUI still has to be added
+(On MacOS the you have to grant CanvasSync access to access the KeyChain. CanvasSync saves the password here. ToDo: Make PopUp to give access if CanvasSync does not have access.)
+
Disclaimer
----------
Please note that by using CanvasSync the user allows the software to authenticate with the Canvas server on the users
diff --git a/bin/canvas.py b/bin/canvas.py
index a64fc36..f142b26 100755
--- a/bin/canvas.py
+++ b/bin/canvas.py
@@ -72,6 +72,7 @@ def run_canvas_sync():
and initializes the program
"""
+ # Executed by command line
# Get command line arguments (C-style)
try:
opts, args = getopt.getopt(sys.argv[1:], u"hsiSp:", [u"help", u"setup", u"info", u"sync", u"password"])
@@ -80,6 +81,7 @@ def run_canvas_sync():
print(err)
usage.help()
+
# Parse the command line arguments and act accordingly
setup = False
show_info = False
diff --git a/resources/macos_statusbar.png b/resources/macos_statusbar.png
new file mode 100644
index 0000000..49f903c
Binary files /dev/null and b/resources/macos_statusbar.png differ
diff --git a/resources/windows_systemtray.png b/resources/windows_systemtray.png
new file mode 100644
index 0000000..e3c8d54
Binary files /dev/null and b/resources/windows_systemtray.png differ