def __init__(self, status, app): self.app = app self.prefs = app.prefs self.status = status self.shell = None self.app_path = app.app_path self.cloud_home = None self.clients_lock = threading.Lock() self.clients = {} try: os.remove(SHELL_PROXY_SOCKET_ADDRESS) except EnvironmentError: pass self.update_prefs() self.thread = StoppableThread(target=self.listen) self.command_to_handler = { 'status': self.broadcast_file_status, 'link': self.share_link, 'folder': self.share_folder, 'browser': self.open_in_browser, 'home': self.send_cloud_home, 'subscribe': self.subscribe_path, }
def restart_core(self, ignore_sync=False): log.info('Application.restart_core: initiating core restart') self.trayicon.wrapper(self.hide_gui_elements) self.trayicon.set_icon("meocloud-init") self.core_client = CoreClient() self.core_listener = CoreListener(CORE_LISTENER_SOCKET_ADDRESS, self.core_client, self.prefs, self, ignore_sync) self.core = Core(self.core_client) # Make sure core isn't running self.stop_threads() # Start the core self.listener_thread = StoppableThread(target=self.core_listener.start) self.watchdog_thread = StoppableThread(target=self.core.watchdog) self.core.thread = self.watchdog_thread self.listener_thread.start() self.watchdog_thread.start() # Restart Shell Proxy and update logging self.shell_proxy.update_prefs() log.info('Application.restart_core: core restart completed')
def move_folder_async(src, dst, callback=None): def move_folder_thread(src, dst, callback): error = True if not os.listdir(dst): os.rmdir(dst) cloud_home = dst else: srcname = os.path.basename(src) cloud_home = os.path.join(dst, srcname) dst = cloud_home try: log = logging.getLogger(LOGGER_NAME) shutil.move(src, dst) log.info('Moved folder {0!r} to {1!r}'.format(src, dst)) error = False except EnvironmentError as err: log.warn('Error while moving folder {0!r} to {1!r}: {2}'.format( src, dst, err)) if callback is not None: GLib.idle_add(callback, cloud_home, error) StoppableThread(target=move_folder_thread, args=(src, dst, callback)).start()
class ShellProxy(object): errormask = select.EPOLLERR | select.EPOLLHUP readmask = select.EPOLLIN | errormask writemask = select.EPOLLOUT def __init__(self, status, app): self.app = app self.prefs = app.prefs self.status = status self.shell = None self.app_path = app.app_path self.cloud_home = None self.clients_lock = threading.Lock() self.clients = {} try: os.remove(SHELL_PROXY_SOCKET_ADDRESS) except EnvironmentError: pass self.update_prefs() self.thread = StoppableThread(target=self.listen) self.command_to_handler = { 'status': self.broadcast_file_status, 'link': self.share_link, 'folder': self.share_folder, 'browser': self.open_in_browser, 'home': self.send_cloud_home, 'subscribe': self.subscribe_path, } def _disconnect(self, client): fd = client.socket.fileno() try: client.epoll.unregister(fd) except IOError: pass try: client.socket.close() except socket.error: pass try: with self.clients_lock: del self.clients[fd] except KeyError: pass def listen(self): server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(SHELL_PROXY_SOCKET_ADDRESS) server_socket.listen(10) server_socket.setblocking(0) epoll = select.epoll() epoll.register(server_socket.fileno(), self.readmask) while not self.thread.stopped(): try: events = epoll.poll(10) except (IOError, OSError) as ioe: if ioe.errno in NONFATAL_SOCKET_ERRORS: continue break for fd, event in events: # New connection if fd == server_socket.fileno(): sock, _ = server_socket.accept() sock.setblocking(0) epoll.register(sock.fileno(), self.readmask) with self.clients_lock: self.clients[sock.fileno()] = Client(sock, epoll) continue client = self.clients.get(fd) if client is None: log.warn('Got activity for unknown client.') epoll.unregister(fd) continue # Socket has data to read if event & select.EPOLLIN: try: received = client.socket.recv(CHUNK_SIZE) except socket.error as se: if se.errno not in NONFATAL_SOCKET_ERRORS: self._disconnect(client) continue # Check for EOF (0 bytes read) if len(received) > 0: client.recvbuf += received else: self._disconnect(client) continue self.process_client_requests(client) # Check if there is data to send if client.sendbuf: epoll.modify(fd, self.readmask | self.writemask) # Socket is ready to write elif event & select.EPOLLOUT: with self.clients_lock: try: bytes_sent = client.socket.send(client.sendbuf) except socket.error as se: if se.errno not in NONFATAL_SOCKET_ERRORS: self._disconnect(client) continue client.sendbuf = client.sendbuf[bytes_sent:] if not client.sendbuf: # No more data to write. Wait for requests only epoll.modify(fd, self.readmask) # Errors and disconnects elif event & self.errormask: self._disconnect(client) for fd, client in self.clients.iteritems(): epoll.unregister(fd) client.socket.close() self.clients.clear() epoll.close() server_socket.close() def process_client_requests(self, client): while True: eol_offset = client.recvbuf.find(PROTO_EOL) if eol_offset == -1: return msg = client.recvbuf[:eol_offset] # + 1 to skip the EOL character client.recvbuf = client.recvbuf[eol_offset + 1:] parts = msg.split(PROTO_SEP) if len(parts) > 1: command = parts[0] path = self.unescape(parts[1]) else: continue handler = self.command_to_handler.get(command) if self.shell and handler: handler(path, client) def unescape(self, path): return path.replace( '\\t', '\t').replace('\\n', '\n').replace('\\\\', '\\') def escape(self, path): escaped = [] for c in path: escaped.append(ESCAPE_MAP.get(c, c)) return ''.join(escaped) filestate_to_code = { FileState.SYNCING: '1', FileState.IGNORED: '2', FileState.ERROR: '2', } def broadcast_file_status(self, path, client=None): state = self.shell.file_states.get(path) if state is not None: code = self.filestate_to_code.get(state, '0') path = self.escape(path) msg = PROTO_SEP.join(('status', path, code)) + PROTO_EOL self._broadcast_msg(msg) else: self.shell.update_file_status(path) def _broadcast_msg(self, msg): with self.clients_lock: for client in self.clients.itervalues(): client.sendbuf += msg client.epoll.modify(client.socket.fileno(), self.readmask | self.writemask) def update_prefs(self): prefs = self.prefs self.cloud_home = prefs.get('Advanced', 'Folder', CLOUD_HOME_DEFAULT_PATH) log.info( 'ShellProxy.update_prefs: cloud_home is {0!r}'. format(self.cloud_home)) msg = PROTO_SEP.join(('home', self.cloud_home, '0')) + PROTO_EOL self._broadcast_msg(msg) def share_folder(self, path, client=None): path = unicode(path).encode('utf-8') if path.startswith(self.cloud_home): path = path.replace(self.cloud_home, '') self.shell.share_folder(path) def share_link(self, path, client=None): path = unicode(path).encode('utf-8') if path.startswith(self.cloud_home): path = path.replace(self.cloud_home, '') self.shell.share_link(path) def open_in_browser(self, path, client=None): path = unicode(path).encode('utf-8') if path.startswith(self.cloud_home): path = path.replace(self.cloud_home, '') self.shell.open_in_browser(path) def send_cloud_home(self, path, client=None): msg = PROTO_SEP.join(('home', self.cloud_home, '0')) + PROTO_EOL with self.clients_lock: client.sendbuf += msg client.epoll.modify(client.socket.fileno(), self.readmask | self.writemask) def subscribe_path(self, path, client=None): path = unicode(path).encode('utf-8') if path.startswith(self.cloud_home): path = path.replace(self.cloud_home, '') self.shell.subscribe_path(path) def start(self): self.thread.start()
class ShellProxy(object): errormask = select.EPOLLERR | select.EPOLLHUP readmask = select.EPOLLIN | errormask writemask = select.EPOLLOUT def __init__(self, status, app): self.app = app self.prefs = app.prefs self.status = status self.shell = None self.app_path = app.app_path self.cloud_home = None self.clients_lock = threading.Lock() self.clients = {} try: os.remove(SHELL_PROXY_SOCKET_ADDRESS) except EnvironmentError: pass self.update_prefs() self.thread = StoppableThread(target=self.listen) self.command_to_handler = { 'status': self.broadcast_file_status, 'link': self.share_link, 'folder': self.share_folder, 'browser': self.open_in_browser, 'home': self.send_cloud_home, 'subscribe': self.subscribe_path, } def _disconnect(self, client): fd = client.socket.fileno() try: client.epoll.unregister(fd) except IOError: pass try: client.socket.close() except socket.error: pass try: with self.clients_lock: del self.clients[fd] except KeyError: pass def listen(self): server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(SHELL_PROXY_SOCKET_ADDRESS) server_socket.listen(10) server_socket.setblocking(0) epoll = select.epoll() epoll.register(server_socket.fileno(), self.readmask) while not self.thread.stopped(): try: events = epoll.poll(10) except (IOError, OSError) as ioe: if ioe.errno in NONFATAL_SOCKET_ERRORS: continue break for fd, event in events: # New connection if fd == server_socket.fileno(): sock, _ = server_socket.accept() sock.setblocking(0) epoll.register(sock.fileno(), self.readmask) with self.clients_lock: self.clients[sock.fileno()] = Client(sock, epoll) continue client = self.clients.get(fd) if client is None: log.warn('Got activity for unknown client.') epoll.unregister(fd) continue # Socket has data to read if event & select.EPOLLIN: try: received = client.socket.recv(CHUNK_SIZE) except socket.error as se: if se.errno not in NONFATAL_SOCKET_ERRORS: self._disconnect(client) continue # Check for EOF (0 bytes read) if len(received) > 0: client.recvbuf += received else: self._disconnect(client) continue self.process_client_requests(client) # Check if there is data to send if client.sendbuf: epoll.modify(fd, self.readmask | self.writemask) # Socket is ready to write elif event & select.EPOLLOUT: with self.clients_lock: try: bytes_sent = client.socket.send(client.sendbuf) except socket.error as se: if se.errno not in NONFATAL_SOCKET_ERRORS: self._disconnect(client) continue client.sendbuf = client.sendbuf[bytes_sent:] if not client.sendbuf: # No more data to write. Wait for requests only epoll.modify(fd, self.readmask) # Errors and disconnects elif event & self.errormask: self._disconnect(client) for fd, client in self.clients.iteritems(): epoll.unregister(fd) client.socket.close() self.clients.clear() epoll.close() server_socket.close() def process_client_requests(self, client): while True: eol_offset = client.recvbuf.find(PROTO_EOL) if eol_offset == -1: return msg = client.recvbuf[:eol_offset] # + 1 to skip the EOL character client.recvbuf = client.recvbuf[eol_offset + 1:] parts = msg.split(PROTO_SEP) if len(parts) > 1: command = parts[0] path = self.unescape(parts[1]) else: continue handler = self.command_to_handler.get(command) if self.shell and handler: handler(path, client) def unescape(self, path): return path.replace('\\t', '\t').replace('\\n', '\n').replace('\\\\', '\\') def escape(self, path): escaped = [] for c in path: escaped.append(ESCAPE_MAP.get(c, c)) return ''.join(escaped) filestate_to_code = { FileState.SYNCING: '1', FileState.IGNORED: '2', FileState.ERROR: '2', } def broadcast_file_status(self, path, client=None): state = self.shell.file_states.get(path) if state is not None: code = self.filestate_to_code.get(state, '0') path = self.escape(path) msg = PROTO_SEP.join(('status', path, code)) + PROTO_EOL self._broadcast_msg(msg) else: self.shell.update_file_status(path) def _broadcast_msg(self, msg): with self.clients_lock: for client in self.clients.itervalues(): client.sendbuf += msg client.epoll.modify(client.socket.fileno(), self.readmask | self.writemask) def update_prefs(self): prefs = self.prefs self.cloud_home = prefs.get('Advanced', 'Folder', CLOUD_HOME_DEFAULT_PATH) log.info('ShellProxy.update_prefs: cloud_home is {0!r}'.format( self.cloud_home)) msg = PROTO_SEP.join(('home', self.cloud_home, '0')) + PROTO_EOL self._broadcast_msg(msg) def share_folder(self, path, client=None): path = unicode(path).encode('utf-8') if path.startswith(self.cloud_home): path = path.replace(self.cloud_home, '') self.shell.share_folder(path) def share_link(self, path, client=None): path = unicode(path).encode('utf-8') if path.startswith(self.cloud_home): path = path.replace(self.cloud_home, '') self.shell.share_link(path) def open_in_browser(self, path, client=None): path = unicode(path).encode('utf-8') if path.startswith(self.cloud_home): path = path.replace(self.cloud_home, '') self.shell.open_in_browser(path) def send_cloud_home(self, path, client=None): msg = PROTO_SEP.join(('home', self.cloud_home, '0')) + PROTO_EOL with self.clients_lock: client.sendbuf += msg client.epoll.modify(client.socket.fileno(), self.readmask | self.writemask) def subscribe_path(self, path, client=None): path = unicode(path).encode('utf-8') if path.startswith(self.cloud_home): path = path.replace(self.cloud_home, '') self.shell.subscribe_path(path) def start(self): self.thread.start()
def on_logout(self, w): log.info('Application.on_logout: initiating logout') self.prefs_window.destroy() StoppableThread(target=self.on_logout_thread).start()
class Application(Gtk.Application): def __init__(self, app_path): Gtk.Application.__init__(self, application_id="pt.meocloud", flags=Gio.ApplicationFlags.FLAGS_NONE) self.connect("activate", self.on_activate) Notify.init('MEO Cloud') self.app_path = app_path self.prefs_window = None self.prefs = Preferences() creds = CredentialStore(self.prefs, utils.rc4_drop768, utils.rc4_drop768, utils.mac, utils.MACSIZE) self.prefs.set_credential_store(creds) # for some reason this only works in __init__ self.trayicon = TrayIcon(self) self.trayicon.show() self.missing_quit = False self.running = False self.paused = False self.in_selective_sync = False self.offline = False self.force_preferences_visible = False self.requires_authorization = True self.enable_sync = True self.core_client = None self.core_listener = None self.log_handler = None self.shell = None self.core = None self.icon_type = "" self.problem_text = "" self.shared = None self.recentfiles_menu = None self.menuitem_recent = None self.menuitem_storage = None self.menuitem_problem = None self.menuitem_moreinfo = None self.menuitem_status = [] self.menuitem_changestatus = None self.menuitem_prefs = None self.storage_separator = None self.sync_thread = None self.menu_thread = None self.listener_thread = None self.watchdog_thread = None self.shell_proxy = ShellProxy(codes.CORE_INITIALIZING, self) # are we running GNOME or elementary OS? self.use_headerbar = utils.use_headerbar() # check if app has been updated every hour self.update_app_timeout = GLib.timeout_add(60000, self.update_app_version) self.update_sync_status_timeout = None def _migrate_cli_settings(self): cli_config_path = os.path.join(CONFIG_PATH, "ui/ui_config.yaml") try: import yaml stream = open(cli_config_path, 'rb') cli_config = yaml.load(stream) except (ImportError, EnvironmentError): return except yaml.YAMLError: log.info('Found invalid CLI configuration.') utils.force_remove(cli_config_path, log.warn) else: cli_rc4_key = '8025c571541a64bccd00135f87dec11a' \ '83a8c5de69c94ec6b642dbdc6a2aebdd' account_dict = cli_config['account'] account_dict['authKey'] = utils.decrypt(account_dict['authKey'], cli_rc4_key) account_dict['clientID'] = utils.decrypt(account_dict['clientID'], cli_rc4_key) self.prefs.creds.cid = account_dict['clientID'] self.prefs.creds.ckey = account_dict['authKey'] self.prefs.put('Account', 'email', str(account_dict['email']).encode('utf-8')) self.prefs.put('Account', 'name', str(account_dict['name']).encode('utf-8')) self.prefs.put('Account', 'deviceName', str(account_dict['deviceName']).encode('utf-8')) self.prefs.put('Advanced', 'Folder', str(cli_config['cloud_home']).encode('utf-8')) self.prefs.save() utils.purge_meta() utils.force_remove(cli_config_path, log.warn) def on_activate(self, data=None): if not self.running: self.running = True self.icon_type = self.prefs.get("General", "Icons", "") self.trayicon.set_icon("meocloud-init") self.force_preferences_visible = \ self.prefs.get("Network", "Proxy", "None") != "None" if not self.prefs.get('Account', 'email'): log.info('Application.on_activate: prefs not initialized.') utils.force_remove( os.path.join(UI_CONFIG_PATH, 'shared_directories')) else: if not os.path.exists( self.prefs.get("Advanced", "Folder", CLOUD_HOME_DEFAULT_PATH)): log.info('Application.on_activate: cloud_home missing') missing = MissingDialog(self) Gdk.threads_enter() missing.run() Gdk.threads_leave() # migrations if self.prefs.creds.cid is None: self._migrate_cli_settings() if not self.missing_quit: try: self.shared = set() path = os.path.join(UI_CONFIG_PATH, 'shared_directories') with open(path, 'rb') as fobj: for line in fobj.readlines(): self.shared.add(line.rstrip('\n')) except EnvironmentError: self.shared = set() utils.create_required_folders(self.prefs) self.log_handler = LogHandler(self.core_client) utils.init_logging(self.log_handler) recentfiles_nothing = Gtk.MenuItem(_("No Recent Files")) recentfiles_nothing.show() self.recentfiles_menu = Gtk.Menu() self.recentfiles_menu.add(recentfiles_nothing) menuitem_folder = Gtk.MenuItem(_("Open Folder")) menuitem_site = Gtk.MenuItem(_("Open Website")) self.menuitem_recent = Gtk.MenuItem(_("Recent Files")) self.menuitem_recent.set_submenu(self.recentfiles_menu) self.menuitem_storage = Gtk.MenuItem("-") self.menuitem_storage.set_sensitive(False) problem_moreinfo_menu = Gtk.Menu() self.menuitem_moreinfo = Gtk.MenuItem(_("More info")) self.menuitem_problem = Gtk.MenuItem( _("There is a problem synchronizing your files")) problem_moreinfo_menu.append(self.menuitem_moreinfo) self.menuitem_problem.set_submenu(problem_moreinfo_menu) self.menuitem_status.append(Gtk.MenuItem(_("Unauthorized"))) self.menuitem_status.append(Gtk.MenuItem("-")) self.menuitem_status.append(Gtk.MenuItem("-")) self.menuitem_status.append(Gtk.MenuItem("-")) self.menuitem_status[0].set_sensitive(False) self.menuitem_status[1].set_sensitive(False) self.menuitem_status[2].set_sensitive(False) self.menuitem_status[3].set_sensitive(False) self.menuitem_changestatus = Gtk.MenuItem(_("Authorize")) self.menuitem_prefs = Gtk.MenuItem(_("Preferences")) menuitem_about = Gtk.MenuItem(_("About")) menuitem_bug = Gtk.MenuItem(_("Report a Bug")) menuitem_quit = Gtk.MenuItem(_("Quit")) self.storage_separator = Gtk.SeparatorMenuItem() self.trayicon.add_menu_item(menuitem_folder) self.trayicon.add_menu_item(menuitem_site) self.trayicon.add_menu_item(self.menuitem_recent) self.trayicon.add_menu_item(self.storage_separator, True) self.trayicon.add_menu_item(self.menuitem_storage, True) self.trayicon.add_menu_item(Gtk.SeparatorMenuItem()) self.trayicon.add_menu_item(self.menuitem_problem, True) self.trayicon.add_menu_item(self.menuitem_status[0], True) self.trayicon.add_menu_item(self.menuitem_status[1], True) self.trayicon.add_menu_item(self.menuitem_status[2], True) self.trayicon.add_menu_item(self.menuitem_status[3], True) self.trayicon.add_menu_item(self.menuitem_changestatus) self.trayicon.add_menu_item(Gtk.SeparatorMenuItem()) self.trayicon.add_menu_item(self.menuitem_prefs, True) self.trayicon.add_menu_item(menuitem_bug) self.trayicon.add_menu_item(menuitem_about) self.trayicon.add_menu_item(menuitem_quit) menuitem_folder.connect("activate", self.open_folder) menuitem_site.connect("activate", self.open_website) self.menuitem_moreinfo.connect("activate", self.show_problem) self.menuitem_changestatus.connect("activate", self.toggle_status) self.menuitem_prefs.connect("activate", self.show_prefs) menuitem_about.connect("activate", lambda w: AboutDialog(self.app_path)) menuitem_bug.connect("activate", self.report_bug) menuitem_quit.connect("activate", lambda w: self.quit()) self.shell_proxy.start() self.restart_core() self.hold() elif self.shell is not None: self.show_prefs(None) def update_app_version(self): try: version_file = open( os.path.join(self.app_path, 'meocloud_gui/VERSION'), 'rb') except IOError: return True else: version = version_file.read().strip() version_file.close() if version != VERSION: cmd = "kill {0} && {1} &".format( os.getpid(), os.path.join(self.app_path, "meocloud-gui")) os.system(cmd) return False else: return True def report_bug(self, w): webbrowser.open("http://ajuda.cld.pt") def show_problem(self, w): messagedialog = Gtk.MessageDialog(parent=None, flags=Gtk.DialogFlags.MODAL, type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK, message_format=self.problem_text) messagedialog.run() messagedialog.destroy() def clean_recent_files(self): for menuitem in self.recentfiles_menu.get_children(): self.recentfiles_menu.remove(menuitem) recentfiles_nothing = Gtk.MenuItem(_("No Recent Files")) self.recentfiles_menu.add(recentfiles_nothing) recentfiles_nothing.show() def update_recent_files(self, recently_changed, cloud_home): if len(recently_changed) > 0: for menuitem in self.recentfiles_menu.get_children(): self.recentfiles_menu.remove(menuitem) for path in recently_changed: display_path = path[2:] menuitem = Gtk.MenuItem(display_path) menuitem.connect( "activate", lambda w: self.open_recent_file(w, cloud_home)) if path.startswith("-/"): menuitem.set_sensitive(False) self.recentfiles_menu.add(menuitem) menuitem.show() def open_recent_file(self, w, cloud_home): path = os.path.join(cloud_home, w.get_label()) path = path.replace(os.path.basename(path), '') webbrowser.open(path) def update_menu(self): if self.requires_authorization: self.requires_authorization = False status = self.core_client.currentStatus() self.update_storage(status.usedQuota, status.totalQuota) cloud_home = self.prefs.get('Advanced', 'Folder', CLOUD_HOME_DEFAULT_PATH) self.shell_proxy.status = status.state if (status.state == codes.CORE_WAITING) and self.enable_sync: self.core_client.startSync(cloud_home) if (self.in_selective_sync and status.state != codes.CORE_SELECTIVE_SYNC): self.in_selective_sync = False if self.prefs_window is not None: GLib.idle_add(self.prefs_window.selective_button.set_sensitive, True) if ((status.state == codes.CORE_SYNCING or status.state == codes.CORE_READY) and self.shell is None): self.shell = Shell(self.shell_proxy) self.shell_proxy.update_prefs() if ((status.state == codes.CORE_SYNCING or status.state == codes.CORE_READY) and self.log_handler.core_client is None): self.log_handler.core_client = self.core_client if status.state != codes.CORE_SYNCING: self.trayicon.wrapper(lambda: self.menuitem_status[0].show()) self.trayicon.wrapper(lambda: self.menuitem_status[1].hide()) self.trayicon.wrapper(lambda: self.menuitem_status[2].hide()) self.trayicon.wrapper(lambda: self.menuitem_status[3].hide()) if (status.state == codes.CORE_INITIALIZING or status.state == codes.CORE_AUTHORIZING or status.state == codes.CORE_WAITING): self.trayicon.wrapper(self.hide_gui_elements) self.trayicon.set_icon("meocloud-init") self.paused = True self.update_sync_status_stop() self.update_status(_("Initializing")) self.update_menu_action(_("Resume")) elif status.state == codes.CORE_SYNCING: self.trayicon.wrapper(self.show_gui_elements) self.trayicon.set_icon("meocloud-sync-1") self.paused = False sync_code = utils.get_sync_code(status.statusCode) self.menu_from_sync_code(sync_code) self.update_sync_status_start() self.update_menu_action(_("Pause")) elif status.state == codes.CORE_READY: self.core_client.ignore_logs = False self.trayicon.wrapper(lambda: self.show_gui_elements(True)) self.trayicon.set_icon("meocloud-ok") self.paused = False self.update_sync_status_stop() self.update_status(_("Synced")) self.update_menu_action(_("Pause")) recently_changed = self.core_client.recentlyChangedFilePaths() self.trayicon.wrapper( lambda: self.update_recent_files(recently_changed, cloud_home)) elif status.state == codes.CORE_PAUSED: self.paused = True self.trayicon.wrapper(self.show_gui_elements) self.trayicon.set_icon("meocloud-pause") self.update_sync_status_stop() self.update_status(_("Paused")) self.update_menu_action(_("Resume")) elif status.state == codes.CORE_SELECTIVE_SYNC: self.in_selective_sync = True if self.prefs_window is not None: GLib.idle_add(self.prefs_window.selective_button.set_sensitive, False) self.trayicon.wrapper(self.show_gui_elements) self.trayicon.set_icon("meocloud-sync-1") self.paused = False self.update_menu_action(_("Applying selective sync settings...")) elif status.state == codes.CORE_OFFLINE: self.trayicon.wrapper(self.show_gui_elements) self.trayicon.set_icon("meocloud-offline") self.paused = True self.offline = True self.update_sync_status_stop() self.update_status(_("Offline")) self.update_menu_action(_("Resume")) elif status.state == codes.CORE_ERROR: self.trayicon.wrapper(self.show_gui_elements) self.trayicon.set_icon("meocloud-error") self.paused = True self.update_sync_status_stop() self.update_status(_("Error")) self.update_menu_action(_("Resume")) error_code = meocloud_gui.utils.get_error_code(status.statusCode) log.warning('CoreListener: Got error code: {0}'.format(error_code)) if error_code == codes.ERROR_AUTH_TIMEOUT: pass elif error_code == codes.ERROR_ROOTFOLDER_GONE: log.warning('CoreListener: Root folder is gone, ' 'will now shutdown') # send a notification about the issue notif_icon = os.path.join(self.app_path, "icons/meocloud.svg") notif_title = _('MEO Cloud Folder Missing') notif_string = _('Your MEO Cloud folder is missing.') with utils.gdk_threads_lock(): notification = Notify.Notification.new( notif_title, notif_string, notif_icon) notification.show() # restart the app so we can deal with the missing folder cmd = "kill {0} && {1} &".format( os.getpid(), os.path.join(self.app_path, "meocloud-gui")) os.system(cmd) elif error_code == codes.ERROR_UNKNOWN: pass elif error_code == codes.ERROR_THREAD_CRASH: pass elif error_code == codes.ERROR_CANNOT_WATCH_FS: log.warning('CoreListener: Cannot watch filesystem, ' 'will now shutdown') else: log.error('CoreListener: Got unknown error code: {0}'.format( error_code)) assert False def update_sync_status(self): if self.update_sync_status_timeout is None: return False try: syncstatus = self.core_client.currentSyncStatus() status = self.core_client.currentStatus() except ListenerConnectionFailedException: self.update_status(_("Syncing"), 0) self.trayicon.wrapper(lambda: self.menuitem_status[0].show()) return False sync_code = utils.get_sync_code(status.statusCode) self.menu_from_sync_code(sync_code) if syncstatus.downloadRate > 0 and syncstatus.pendingDownloads > 0: self.update_status( _("Downloading {0} file(s) at {1}/s... ({2})").format( syncstatus.pendingDownloads, utils.convert_size(syncstatus.downloadRate), utils.convert_time(syncstatus.downloadETASecs)), 1) elif syncstatus.pendingDownloads > 0: self.update_status( _("Downloading {0} file(s)...").format( syncstatus.pendingDownloads), 1) if syncstatus.uploadRate > 0 and syncstatus.pendingUploads > 0: self.update_status( _("Uploading {0} file(s) at {1}/s... ({2})").format( syncstatus.pendingUploads, utils.convert_size(syncstatus.uploadRate), utils.convert_time(syncstatus.uploadETASecs)), 2) elif syncstatus.pendingUploads > 0: self.update_status( _("Uploading {0} file(s)...").format( syncstatus.pendingUploads), 2) if syncstatus.pendingIndexes > 0: self.update_status( _("Indexing {0} file(s)...").format(syncstatus.pendingIndexes), 3) return True def menu_from_sync_code(self, sync_code): if sync_code & codes.SYNC_LISTING_CHANGES: self.update_status(_("Listing remote changes..."), 0) self.trayicon.wrapper(lambda: self.menuitem_status[0].show()) else: self.trayicon.wrapper(lambda: self.menuitem_status[0].hide()) if sync_code & codes.SYNC_DOWNLOADING: self.update_status(_("Downloading files..."), 1) self.trayicon.wrapper(lambda: self.menuitem_status[1].show()) else: self.trayicon.wrapper(lambda: self.menuitem_status[1].hide()) if sync_code & codes.SYNC_UPLOADING: self.update_status(_("Uploading files..."), 2) self.trayicon.wrapper(lambda: self.menuitem_status[2].show()) else: self.trayicon.wrapper(lambda: self.menuitem_status[2].hide()) if sync_code & codes.SYNC_INDEXING: self.update_status(_("Indexing files..."), 3) self.trayicon.wrapper(lambda: self.menuitem_status[3].show()) else: self.trayicon.wrapper(lambda: self.menuitem_status[3].hide()) def update_sync_status_stop(self): if self.update_sync_status_timeout is not None: GLib.source_remove(self.update_sync_status_timeout) self.update_sync_status_timeout = None def update_sync_status_start(self): if self.update_sync_status_timeout is None: self.update_sync_status() self.update_sync_status_timeout = \ GLib.timeout_add(5000, self.update_sync_status) def hide_gui_elements(self): self.storage_separator.hide() if self.force_preferences_visible: self.menuitem_prefs.show() else: self.menuitem_prefs.hide() self.menuitem_storage.hide() def show_gui_elements(self, storage=False): self.menuitem_prefs.show() if storage: self.storage_separator.show() self.menuitem_storage.show() def update_status(self, status, num=0): self.trayicon.wrapper( lambda: self.menuitem_status[num].set_label(status)) def update_menu_action(self, action): self.trayicon.wrapper( lambda: self.menuitem_changestatus.set_label(action)) def toggle_status(self, w): if self.offline: self.offline = False self.paused = False self.restart_core() elif self.requires_authorization: self.restart_core() elif self.paused: self.core_client.unpause() else: self.core_client.pause() def update_storage(self, used, total): if total > 0: used_percentage = (used * 100) / total else: used_percentage = 100 used_percentage = str(used_percentage) + "%" total = utils.convert_size(total) self.menuitem_storage.set_label( _("{0} of {1} used").format(str(used_percentage), str(total))) def show_prefs(self, w): if not self.prefs_window: self.prefs_window = PrefsWindow(self) self.prefs_window.logout_button.connect("clicked", self.on_logout) self.prefs_window.show_all() self.prefs_window.present() def on_logout(self, w): log.info('Application.on_logout: initiating logout') self.prefs_window.destroy() StoppableThread(target=self.on_logout_thread).start() def on_logout_thread(self): meocloud_gui.core.api.unlink(self.core_client, self.prefs) utils.force_remove(os.path.join(UI_CONFIG_PATH, 'prefs.ini')) utils.force_remove(os.path.join(UI_CONFIG_PATH, 'shared_directories')) utils.purge_all() self.requires_authorization = True self.update_status(_("Unauthorized")) self.update_menu_action(_("Authorize")) self.trayicon.wrapper(self.clean_recent_files) self.trayicon.wrapper(self.hide_gui_elements) log.info('Application.on_logout_thread: completing logout') self.restart_core() def open_folder(self, w): if self.prefs: cloud_home = self.prefs.get('Advanced', 'Folder', CLOUD_HOME_DEFAULT_PATH) else: cloud_home = CLOUD_HOME_DEFAULT_PATH webbrowser.open(cloud_home) def open_website(self, w): webbrowser.open(self.core_client.webLoginURL()) def restart_core(self, ignore_sync=False): log.info('Application.restart_core: initiating core restart') self.trayicon.wrapper(self.hide_gui_elements) self.trayicon.set_icon("meocloud-init") self.core_client = CoreClient() self.core_listener = CoreListener(CORE_LISTENER_SOCKET_ADDRESS, self.core_client, self.prefs, self, ignore_sync) self.core = Core(self.core_client) # Make sure core isn't running self.stop_threads() # Start the core self.listener_thread = StoppableThread(target=self.core_listener.start) self.watchdog_thread = StoppableThread(target=self.core.watchdog) self.core.thread = self.watchdog_thread self.listener_thread.start() self.watchdog_thread.start() # Restart Shell Proxy and update logging self.shell_proxy.update_prefs() log.info('Application.restart_core: core restart completed') def stop_threads(self): self.log_handler.core_client = None if (self.listener_thread is not None and not self.listener_thread.stopped()): self.listener_thread.stop() if (self.watchdog_thread is not None and not self.watchdog_thread.stopped()): self.watchdog_thread.stop() self.shell = None if (self.core.thread is not None and not self.core.thread.stopped()): self.core.thread.stop() if self.core is not None: self.core.stop() log.info('Application.stop_threads: threads stopped') def quit(self): self.stop_threads() log.info('Application.quit: shutting down') self.running = False Gtk.Application.quit(self)
class Application(Gtk.Application): def __init__(self, app_path): Gtk.Application.__init__(self, application_id="pt.meocloud", flags=Gio.ApplicationFlags.FLAGS_NONE) self.connect("activate", self.on_activate) Notify.init('MEO Cloud') self.app_path = app_path self.prefs_window = None self.prefs = Preferences() creds = CredentialStore(self.prefs, utils.rc4_drop768, utils.rc4_drop768, utils.mac, utils.MACSIZE) self.prefs.set_credential_store(creds) # for some reason this only works in __init__ self.trayicon = TrayIcon(self) self.trayicon.show() self.missing_quit = False self.running = False self.paused = False self.in_selective_sync = False self.offline = False self.force_preferences_visible = False self.requires_authorization = True self.enable_sync = True self.core_client = None self.core_listener = None self.log_handler = None self.shell = None self.core = None self.icon_type = "" self.problem_text = "" self.shared = None self.recentfiles_menu = None self.menuitem_recent = None self.menuitem_storage = None self.menuitem_problem = None self.menuitem_moreinfo = None self.menuitem_status = [] self.menuitem_changestatus = None self.menuitem_prefs = None self.storage_separator = None self.sync_thread = None self.menu_thread = None self.listener_thread = None self.watchdog_thread = None self.shell_proxy = ShellProxy(codes.CORE_INITIALIZING, self) # are we running GNOME or elementary OS? self.use_headerbar = utils.use_headerbar() # check if app has been updated every hour self.update_app_timeout = GLib.timeout_add(60000, self.update_app_version) self.update_sync_status_timeout = None def _migrate_cli_settings(self): cli_config_path = os.path.join(CONFIG_PATH, "ui/ui_config.yaml") try: import yaml stream = open(cli_config_path, 'rb') cli_config = yaml.load(stream) except (ImportError, EnvironmentError): return except yaml.YAMLError: log.info('Found invalid CLI configuration.') utils.force_remove(cli_config_path, log.warn) else: cli_rc4_key = '8025c571541a64bccd00135f87dec11a' \ '83a8c5de69c94ec6b642dbdc6a2aebdd' account_dict = cli_config['account'] account_dict['authKey'] = utils.decrypt( account_dict['authKey'], cli_rc4_key) account_dict['clientID'] = utils.decrypt( account_dict['clientID'], cli_rc4_key) self.prefs.creds.cid = account_dict['clientID'] self.prefs.creds.ckey = account_dict['authKey'] self.prefs.put('Account', 'email', unicode(account_dict['email']).encode('utf-8')) self.prefs.put('Account', 'name', unicode(account_dict['name']).encode('utf-8')) self.prefs.put('Account', 'deviceName', unicode(account_dict['deviceName']).encode('utf-8')) self.prefs.put('Advanced', 'Folder', unicode(cli_config['cloud_home']).encode('utf-8')) self.prefs.save() utils.purge_meta() utils.force_remove(cli_config_path, log.warn) def on_activate(self, data=None): if not self.running: self.running = True self.icon_type = self.prefs.get("General", "Icons", "") self.trayicon.set_icon("meocloud-init") self.force_preferences_visible = \ self.prefs.get("Network", "Proxy", "None") != "None" if not self.prefs.get('Account', 'email'): log.info('Application.on_activate: prefs not initialized.') utils.force_remove(os.path.join(UI_CONFIG_PATH, 'shared_directories')) else: if not os.path.exists(self.prefs.get("Advanced", "Folder", CLOUD_HOME_DEFAULT_PATH)): log.info('Application.on_activate: cloud_home missing') missing = MissingDialog(self) Gdk.threads_enter() missing.run() Gdk.threads_leave() # migrations if self.prefs.creds.cid is None: self._migrate_cli_settings() if not self.missing_quit: try: self.shared = set() path = os.path.join(UI_CONFIG_PATH, 'shared_directories') with open(path, 'rb') as fobj: for line in fobj.readlines(): self.shared.add(line.rstrip('\n')) except EnvironmentError: self.shared = set() utils.create_required_folders(self.prefs) self.log_handler = LogHandler(self.core_client) utils.init_logging(self.log_handler) recentfiles_nothing = Gtk.MenuItem(_("No Recent Files")) recentfiles_nothing.show() self.recentfiles_menu = Gtk.Menu() self.recentfiles_menu.add(recentfiles_nothing) menuitem_folder = Gtk.MenuItem(_("Open Folder")) menuitem_site = Gtk.MenuItem(_("Open Website")) self.menuitem_recent = Gtk.MenuItem(_("Recent Files")) self.menuitem_recent.set_submenu(self.recentfiles_menu) self.menuitem_storage = Gtk.MenuItem("-") self.menuitem_storage.set_sensitive(False) problem_moreinfo_menu = Gtk.Menu() self.menuitem_moreinfo = Gtk.MenuItem(_("More info")) self.menuitem_problem = Gtk.MenuItem( _("There is a problem synchronizing your files")) problem_moreinfo_menu.append(self.menuitem_moreinfo) self.menuitem_problem.set_submenu(problem_moreinfo_menu) self.menuitem_status.append(Gtk.MenuItem(_("Unauthorized"))) self.menuitem_status.append(Gtk.MenuItem("-")) self.menuitem_status.append(Gtk.MenuItem("-")) self.menuitem_status.append(Gtk.MenuItem("-")) self.menuitem_status[0].set_sensitive(False) self.menuitem_status[1].set_sensitive(False) self.menuitem_status[2].set_sensitive(False) self.menuitem_status[3].set_sensitive(False) self.menuitem_changestatus = Gtk.MenuItem(_("Authorize")) self.menuitem_prefs = Gtk.MenuItem(_("Preferences")) menuitem_about = Gtk.MenuItem(_("About")) menuitem_bug = Gtk.MenuItem(_("Report a Bug")) menuitem_quit = Gtk.MenuItem(_("Quit")) self.storage_separator = Gtk.SeparatorMenuItem() self.trayicon.add_menu_item(menuitem_folder) self.trayicon.add_menu_item(menuitem_site) self.trayicon.add_menu_item(self.menuitem_recent) self.trayicon.add_menu_item(self.storage_separator, True) self.trayicon.add_menu_item(self.menuitem_storage, True) self.trayicon.add_menu_item(Gtk.SeparatorMenuItem()) self.trayicon.add_menu_item(self.menuitem_problem, True) self.trayicon.add_menu_item(self.menuitem_status[0], True) self.trayicon.add_menu_item(self.menuitem_status[1], True) self.trayicon.add_menu_item(self.menuitem_status[2], True) self.trayicon.add_menu_item(self.menuitem_status[3], True) self.trayicon.add_menu_item(self.menuitem_changestatus) self.trayicon.add_menu_item(Gtk.SeparatorMenuItem()) self.trayicon.add_menu_item(self.menuitem_prefs, True) self.trayicon.add_menu_item(menuitem_bug) self.trayicon.add_menu_item(menuitem_about) self.trayicon.add_menu_item(menuitem_quit) menuitem_folder.connect("activate", self.open_folder) menuitem_site.connect("activate", self.open_website) self.menuitem_moreinfo.connect("activate", self.show_problem) self.menuitem_changestatus.connect("activate", self.toggle_status) self.menuitem_prefs.connect("activate", self.show_prefs) menuitem_about.connect( "activate", lambda w: AboutDialog(self.app_path)) menuitem_bug.connect("activate", self.report_bug) menuitem_quit.connect("activate", lambda w: self.quit()) self.shell_proxy.start() self.restart_core() self.hold() elif self.shell is not None: self.show_prefs(None) def update_app_version(self): try: version_file = open( os.path.join(self.app_path, 'meocloud_gui/VERSION'), 'rb') except IOError: return True else: version = version_file.read().strip() version_file.close() if version != VERSION: cmd = "kill {0} && {1} &".format( os.getpid(), os.path.join(self.app_path, "meocloud-gui")) os.system(cmd) return False else: return True def report_bug(self, w): webbrowser.open("http://ajuda.cld.pt") def show_problem(self, w): messagedialog = Gtk.MessageDialog(parent=None, flags=Gtk.DialogFlags.MODAL, type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK, message_format=self.problem_text) messagedialog.run() messagedialog.destroy() def clean_recent_files(self): for menuitem in self.recentfiles_menu.get_children(): self.recentfiles_menu.remove(menuitem) recentfiles_nothing = Gtk.MenuItem(_("No Recent Files")) self.recentfiles_menu.add(recentfiles_nothing) recentfiles_nothing.show() def update_recent_files(self, recently_changed, cloud_home): if len(recently_changed) > 0: for menuitem in self.recentfiles_menu.get_children(): self.recentfiles_menu.remove(menuitem) for path in recently_changed: display_path = path[2:] menuitem = Gtk.MenuItem(display_path) menuitem.connect("activate", lambda w: self.open_recent_file(w, cloud_home)) if path.startswith("-/"): menuitem.set_sensitive(False) self.recentfiles_menu.add(menuitem) menuitem.show() def open_recent_file(self, w, cloud_home): path = os.path.join(cloud_home, w.get_label()) path = path.replace(os.path.basename(path), '') webbrowser.open(path) def update_menu(self): if self.requires_authorization: self.requires_authorization = False status = self.core_client.currentStatus() self.update_storage(status.usedQuota, status.totalQuota) cloud_home = self.prefs.get('Advanced', 'Folder', CLOUD_HOME_DEFAULT_PATH) self.shell_proxy.status = status.state if (status.state == codes.CORE_WAITING) and self.enable_sync: self.core_client.startSync(cloud_home) if (self.in_selective_sync and status.state != codes.CORE_SELECTIVE_SYNC): self.in_selective_sync = False if self.prefs_window is not None: GLib.idle_add( self.prefs_window.selective_button.set_sensitive, True) if ((status.state == codes.CORE_SYNCING or status.state == codes.CORE_READY) and self.shell is None): self.shell = Shell(self.shell_proxy) self.shell_proxy.update_prefs() if ((status.state == codes.CORE_SYNCING or status.state == codes.CORE_READY) and self.log_handler.core_client is None): self.log_handler.core_client = self.core_client if status.state != codes.CORE_SYNCING: self.trayicon.wrapper(lambda: self.menuitem_status[0].show()) self.trayicon.wrapper(lambda: self.menuitem_status[1].hide()) self.trayicon.wrapper(lambda: self.menuitem_status[2].hide()) self.trayicon.wrapper(lambda: self.menuitem_status[3].hide()) if (status.state == codes.CORE_INITIALIZING or status.state == codes.CORE_AUTHORIZING or status.state == codes.CORE_WAITING): self.trayicon.wrapper(self.hide_gui_elements) self.trayicon.set_icon("meocloud-init") self.paused = True self.update_sync_status_stop() self.update_status(_("Initializing")) self.update_menu_action(_("Resume")) elif status.state == codes.CORE_SYNCING: self.trayicon.wrapper(self.show_gui_elements) self.trayicon.set_icon("meocloud-sync-1") self.paused = False sync_code = utils.get_sync_code(status.statusCode) self.menu_from_sync_code(sync_code) self.update_sync_status_start() self.update_menu_action(_("Pause")) elif status.state == codes.CORE_READY: self.core_client.ignore_logs = False self.trayicon.wrapper(lambda: self.show_gui_elements(True)) self.trayicon.set_icon("meocloud-ok") self.paused = False self.update_sync_status_stop() self.update_status(_("Synced")) self.update_menu_action(_("Pause")) recently_changed = self.core_client.recentlyChangedFilePaths() self.trayicon.wrapper( lambda: self.update_recent_files(recently_changed, cloud_home)) elif status.state == codes.CORE_PAUSED: self.paused = True self.trayicon.wrapper(self.show_gui_elements) self.trayicon.set_icon("meocloud-pause") self.update_sync_status_stop() self.update_status(_("Paused")) self.update_menu_action(_("Resume")) elif status.state == codes.CORE_SELECTIVE_SYNC: self.in_selective_sync = True if self.prefs_window is not None: GLib.idle_add( self.prefs_window.selective_button.set_sensitive, False) self.trayicon.wrapper(self.show_gui_elements) self.trayicon.set_icon("meocloud-sync-1") self.paused = False self.update_menu_action(_("Applying selective sync settings...")) elif status.state == codes.CORE_OFFLINE: self.trayicon.wrapper(self.show_gui_elements) self.trayicon.set_icon("meocloud-offline") self.paused = True self.offline = True self.update_sync_status_stop() self.update_status(_("Offline")) self.update_menu_action(_("Resume")) elif status.state == codes.CORE_ERROR: self.trayicon.wrapper(self.show_gui_elements) self.trayicon.set_icon("meocloud-error") self.paused = True self.update_sync_status_stop() self.update_status(_("Error")) self.update_menu_action(_("Resume")) error_code = meocloud_gui.utils.get_error_code( status.statusCode) log.warning('CoreListener: Got error code: {0}'.format( error_code)) if error_code == codes.ERROR_AUTH_TIMEOUT: pass elif error_code == codes.ERROR_ROOTFOLDER_GONE: log.warning('CoreListener: Root folder is gone, ' 'will now shutdown') # send a notification about the issue notif_icon = os.path.join( self.app_path, "icons/meocloud.svg") notif_title = _('MEO Cloud Folder Missing') notif_string = _('Your MEO Cloud folder is missing.') with utils.gdk_threads_lock(): notification = Notify.Notification.new(notif_title, notif_string, notif_icon) notification.show() # restart the app so we can deal with the missing folder cmd = "kill {0} && {1} &".format( os.getpid(), os.path.join(self.app_path, "meocloud-gui")) os.system(cmd) elif error_code == codes.ERROR_UNKNOWN: pass elif error_code == codes.ERROR_THREAD_CRASH: pass elif error_code == codes.ERROR_CANNOT_WATCH_FS: log.warning('CoreListener: Cannot watch filesystem, ' 'will now shutdown') else: log.error( 'CoreListener: Got unknown error code: {0}'.format( error_code)) assert False def update_sync_status(self): if self.update_sync_status_timeout is None: return False try: syncstatus = self.core_client.currentSyncStatus() status = self.core_client.currentStatus() except ListenerConnectionFailedException: self.update_status(_("Syncing"), 0) self.trayicon.wrapper(lambda: self.menuitem_status[0].show()) return False sync_code = utils.get_sync_code(status.statusCode) self.menu_from_sync_code(sync_code) if syncstatus.downloadRate > 0 and syncstatus.pendingDownloads > 0: self.update_status( _("Downloading {0} file(s) at {1}/s... ({2})").format( syncstatus.pendingDownloads, utils.convert_size(syncstatus.downloadRate), utils.convert_time(syncstatus.downloadETASecs)), 1) elif syncstatus.pendingDownloads > 0: self.update_status( _("Downloading {0} file(s)...").format( syncstatus.pendingDownloads), 1) if syncstatus.uploadRate > 0 and syncstatus.pendingUploads > 0: self.update_status( _("Uploading {0} file(s) at {1}/s... ({2})").format( syncstatus.pendingUploads, utils.convert_size(syncstatus.uploadRate), utils.convert_time(syncstatus.uploadETASecs)), 2) elif syncstatus.pendingUploads > 0: self.update_status( _("Uploading {0} file(s)...").format( syncstatus.pendingUploads), 2) if syncstatus.pendingIndexes > 0: self.update_status( _("Indexing {0} file(s)...").format( syncstatus.pendingIndexes), 3) return True def menu_from_sync_code(self, sync_code): if sync_code & codes.SYNC_LISTING_CHANGES: self.update_status(_("Listing remote changes..."), 0) self.trayicon.wrapper(lambda: self.menuitem_status[0].show()) else: self.trayicon.wrapper(lambda: self.menuitem_status[0].hide()) if sync_code & codes.SYNC_DOWNLOADING: self.update_status(_("Downloading files..."), 1) self.trayicon.wrapper(lambda: self.menuitem_status[1].show()) else: self.trayicon.wrapper(lambda: self.menuitem_status[1].hide()) if sync_code & codes.SYNC_UPLOADING: self.update_status(_("Uploading files..."), 2) self.trayicon.wrapper(lambda: self.menuitem_status[2].show()) else: self.trayicon.wrapper(lambda: self.menuitem_status[2].hide()) if sync_code & codes.SYNC_INDEXING: self.update_status(_("Indexing files..."), 3) self.trayicon.wrapper(lambda: self.menuitem_status[3].show()) else: self.trayicon.wrapper(lambda: self.menuitem_status[3].hide()) def update_sync_status_stop(self): if self.update_sync_status_timeout is not None: GLib.source_remove(self.update_sync_status_timeout) self.update_sync_status_timeout = None def update_sync_status_start(self): if self.update_sync_status_timeout is None: self.update_sync_status() self.update_sync_status_timeout = \ GLib.timeout_add(5000, self.update_sync_status) def hide_gui_elements(self): self.storage_separator.hide() if self.force_preferences_visible: self.menuitem_prefs.show() else: self.menuitem_prefs.hide() self.menuitem_storage.hide() def show_gui_elements(self, storage=False): self.menuitem_prefs.show() if storage: self.storage_separator.show() self.menuitem_storage.show() def update_status(self, status, num=0): self.trayicon.wrapper( lambda: self.menuitem_status[num].set_label(status)) def update_menu_action(self, action): self.trayicon.wrapper( lambda: self.menuitem_changestatus.set_label(action)) def toggle_status(self, w): if self.offline: self.offline = False self.paused = False self.restart_core() elif self.requires_authorization: self.restart_core() elif self.paused: self.core_client.unpause() else: self.core_client.pause() def update_storage(self, used, total): if total > 0: used_percentage = (used * 100) / total else: used_percentage = 100 used_percentage = str(used_percentage) + "%" total = utils.convert_size(total) self.menuitem_storage.set_label( _("{0} of {1} used").format(str(used_percentage), str(total))) def show_prefs(self, w): if not self.prefs_window: self.prefs_window = PrefsWindow(self) self.prefs_window.logout_button.connect("clicked", self.on_logout) self.prefs_window.show_all() self.prefs_window.present() def on_logout(self, w): log.info('Application.on_logout: initiating logout') self.prefs_window.destroy() StoppableThread(target=self.on_logout_thread).start() def on_logout_thread(self): meocloud_gui.core.api.unlink(self.core_client, self.prefs) utils.force_remove(os.path.join(UI_CONFIG_PATH, 'prefs.ini')) utils.force_remove(os.path.join(UI_CONFIG_PATH, 'shared_directories')) utils.purge_all() self.requires_authorization = True self.update_status(_("Unauthorized")) self.update_menu_action(_("Authorize")) self.trayicon.wrapper(self.clean_recent_files) self.trayicon.wrapper(self.hide_gui_elements) log.info('Application.on_logout_thread: completing logout') self.restart_core() def open_folder(self, w): if self.prefs: cloud_home = self.prefs.get('Advanced', 'Folder', CLOUD_HOME_DEFAULT_PATH) else: cloud_home = CLOUD_HOME_DEFAULT_PATH webbrowser.open(cloud_home) def open_website(self, w): webbrowser.open(self.core_client.webLoginURL()) def restart_core(self, ignore_sync=False): log.info('Application.restart_core: initiating core restart') self.trayicon.wrapper(self.hide_gui_elements) self.trayicon.set_icon("meocloud-init") self.core_client = CoreClient() self.core_listener = CoreListener(CORE_LISTENER_SOCKET_ADDRESS, self.core_client, self.prefs, self, ignore_sync) self.core = Core(self.core_client) # Make sure core isn't running self.stop_threads() # Start the core self.listener_thread = StoppableThread(target=self.core_listener.start) self.watchdog_thread = StoppableThread(target=self.core.watchdog) self.core.thread = self.watchdog_thread self.listener_thread.start() self.watchdog_thread.start() # Restart Shell Proxy and update logging self.shell_proxy.update_prefs() log.info('Application.restart_core: core restart completed') def stop_threads(self): self.log_handler.core_client = None if (self.listener_thread is not None and not self.listener_thread.stopped()): self.listener_thread.stop() if (self.watchdog_thread is not None and not self.watchdog_thread.stopped()): self.watchdog_thread.stop() self.shell = None if (self.core.thread is not None and not self.core.thread.stopped()): self.core.thread.stop() if self.core is not None: self.core.stop() log.info('Application.stop_threads: threads stopped') def quit(self): self.stop_threads() log.info('Application.quit: shutting down') self.running = False Gtk.Application.quit(self)