예제 #1
0
    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')
예제 #3
0
    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')
예제 #4
0
    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,
        }
예제 #5
0
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()
예제 #6
0
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()
예제 #7
0
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)
예제 #10
0
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)