def __init__(self, builder): self._model = None self._win = builder.get_object('applicationwindow', target=self, include_children=True) self._database_menu = builder.get_object('menu_databases', target=self, include_children=True) self.credentials_dialog = CredentialsDialog(builder) self.new_database_dialog = NewDatabaseDialog(builder) self.new_single_replication_dialog = NewSingleReplicationDialog(builder) self.new_multiple_replication_dialog = NewMultipleReplicationDialog(builder) self.delete_databases_dialog = DeleteDatabasesDialog(builder) self._new_replications_window = NewReplicationsWindow(builder, self.on_hide_new_replication_window) self.remote_replication_dialog = RemoteReplicationDialog(builder) self.about_dialog = AboutDialog(builder) self._main_window_view_model = MainWindowViewModel(self._win, self._new_replications_window) self._databases = DatabasesViewModel(self.treeview_databases) del self.treeview_databases self._replication_tasks = ReplicationTasksViewModel(self.treeview_tasks) del self.treeview_tasks self._replication_queue = NewReplicationQueue(self.report_error) self._auto_update = False self._auto_update_exit = threading.Event() self._auto_update_thread = threading.Thread(target=self.auto_update_handler) self._auto_update_thread.daemon = True self._auto_update_thread.start() self._connection_bar = ConnectionBarViewModel(self.entry_server, self.comboboxtext_port, self.checkbutton_secure) del self.entry_server del self.comboboxtext_port del self.checkbutton_secure self._statusbar = StatusBarViewModel(self.statusbar, self.spinner_auto_update) del self.statusbar del self.spinner_auto_update self._statusbar.reset() self._infobar_warnings = InfobarWarningsViewModel(self.infobar_warnings, self.infobar_warnings_message) del self.infobar_warnings del self.infobar_warnings_message self._win.show_all()
class MainWindow: _watch_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH) def __init__(self, builder): self._model = None self._win = builder.get_object('applicationwindow', target=self, include_children=True) self._database_menu = builder.get_object('menu_databases', target=self, include_children=True) self.credentials_dialog = CredentialsDialog(builder) self.new_database_dialog = NewDatabaseDialog(builder) self.new_single_replication_dialog = NewSingleReplicationDialog(builder) self.new_multiple_replication_dialog = NewMultipleReplicationDialog(builder) self.delete_databases_dialog = DeleteDatabasesDialog(builder) self._new_replications_window = NewReplicationsWindow(builder, self.on_hide_new_replication_window) self.remote_replication_dialog = RemoteReplicationDialog(builder) self.about_dialog = AboutDialog(builder) self._main_window_view_model = MainWindowViewModel(self._win, self._new_replications_window) self._databases = DatabasesViewModel(self.treeview_databases) del self.treeview_databases self._replication_tasks = ReplicationTasksViewModel(self.treeview_tasks) del self.treeview_tasks self._replication_queue = NewReplicationQueue(self.report_error) self._auto_update = False self._auto_update_exit = threading.Event() self._auto_update_thread = threading.Thread(target=self.auto_update_handler) self._auto_update_thread.daemon = True self._auto_update_thread.start() self._connection_bar = ConnectionBarViewModel(self.entry_server, self.comboboxtext_port, self.checkbutton_secure) del self.entry_server del self.comboboxtext_port del self.checkbutton_secure self._statusbar = StatusBarViewModel(self.statusbar, self.spinner_auto_update) del self.statusbar del self.spinner_auto_update self._statusbar.reset() self._infobar_warnings = InfobarWarningsViewModel(self.infobar_warnings, self.infobar_warnings_message) del self.infobar_warnings del self.infobar_warnings_message self._win.show_all() def auto_update_handler(self): while not self._auto_update_exit.wait(5): if self._model and self._auto_update: try: self._statusbar.show_busy_spinner(True) self._replication_tasks.update(self._model.replication_tasks) self._databases.update(self._model.databases) except Exception as e: self.report_error(e) finally: self._statusbar.show_busy_spinner(False) # TODO: rename as model_request def couchdb_request(self, func): if self._model: self._main_window_view_model.set_watch_cursor() def task(): nonlocal func try: func() except Exception as e: self.report_error(e) finally: self._main_window_view_model.set_default_cursor() thread = threading.Thread(target=task) thread.start() @GtkHelper.invoke_func_sync def get_credentials(self, server_url): credentials = Keyring.get_auth(server_url) username = credentials.username if credentials else None password = credentials.password if credentials else None result = None if self.credentials_dialog.run(server_url, username, password) == Gtk.ResponseType.OK: result = self.credentials_dialog.credentials if self.credentials_dialog.save_credentials: Keyring.set_auth(server_url, result.username, result.password) self._statusbar.update(self._model) return result def close(self): self._auto_update_exit.set() self._auto_update_thread.join() Gtk.main_quit() @GtkHelper.invoke_func def report_error(self, err): self._infobar_warnings.message = err def queue_replication(self, repl): ref = self._new_replications_window.add(repl) self._replication_queue.put(repl, lambda: self._new_replications_window.update_success(ref), lambda err: self._new_replications_window.update_failed(ref, err)) def set_selected_databases_limit(self, limit): selected_databases = [item for item in self._databases.selected if item.db_name[0] != '_'] if len(selected_databases) > 0: def func(): for row in selected_databases: self._model.set_revs_limit(row.db_name, limit) self.couchdb_request(func) # region Properties @property def server(self): return self._connection_bar.server @property def port(self): return self._connection_bar.port @property def secure(self): return self._connection_bar.secure # endregion # region Event handlers def on_button_connect(self, *_): self._model = None self._infobar_warnings.show(False) self._replication_tasks.clear() self._databases.clear() self._statusbar.reset() self._main_window_view_model.reset_window_titles() try: self._model = MainWindowModel(self.server, self.port, self.secure, self.get_credentials) def request(): self._databases.update(self._model.databases) self._replication_tasks.update(self._model.replication_tasks) self._statusbar.update(self._model) self._main_window_view_model.update_window_titles(self._model) self._connection_bar.append_server_to_history(self.server) self.couchdb_request(request) except Exception as e: self.report_error(e) def on_infobar_warnings_response(self, *_): self._infobar_warnings.show(False) def on_menu_databases_refresh(self, *_): self._databases.update(self._model.databases) def on_comboboxtext_port_changed(self, *_): self._connection_bar.on_comboboxtext_port_changed() def on_menuitem_file_new_window_activate(self, *_): try: Popen([sys.executable, ' '.join(sys.argv)]) except Exception as e: self.report_error(e) def on_database_button_press_event(self, _, event): if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3: self._database_menu.popup(None, None, None, None, event.button, event.time) return True def on_databases_popup_menu(self, *_): self._database_menu.popup(None, None, None, None, 0, 0) return True def on_menu_databases_new(self, *_): if self.new_database_dialog.run() == Gtk.ResponseType.OK: name = self.new_database_dialog.name def request(): self._model.create_database(name) db = self._model.get_database(name) self._databases.append(db) self.couchdb_request(request) def on_menu_databases_delete(self, *_): selected_databases = [item for item in self._databases.selected if item.db_name[0] != '_'] if len(selected_databases) > 0: result = self.delete_databases_dialog.run(selected_databases) if result == Gtk.ResponseType.OK: def request(): for db in reversed(self.delete_databases_dialog.selected_databases): self._model.delete_database(db.db_name) self._databases.remove(db.db_name) self.couchdb_request(request) def on_menu_databases_backup(self, *_): for selected_database in self._databases.selected: if selected_database.db_name.find('backup$') < 0: backup_database = True source_name = selected_database.db_name target_name = 'backup$' + source_name try: self._model.get_database(target_name) response = GtkHelper.run_dialog( self._win, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Target database '{}' already exists, continue?".format(target_name)) backup_database = response is Gtk.ResponseType.YES except: pass if backup_database: repl = Replication(self._model, source_name, target_name, drop_first=True, create=True) self.queue_replication(repl) def on_menu_databases_restore(self, *_): selected_databases = self._databases.selected if len(selected_databases) == 1 and selected_databases[0].db_name.find('backup$') == 0: restore_database = True source_name = selected_databases[0].db_name target_name = source_name[7::] try: self._model.get_database(target_name) response = GtkHelper.run_dialog(self._win, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Target database already exists, continue?") restore_database = response is Gtk.ResponseType.YES except: pass if restore_database: repl = Replication(self._model, source_name, target_name, drop_first=True, create=True) self.queue_replication(repl) def on_menuitem_databases_compact(self, *_): selected_databases = self._databases.selected if len(selected_databases) > 0: def func(): for selected_database in selected_databases: self._model.compact_database(selected_database.db_name) self.couchdb_request(func) def on_menu_databases_browse_futon(self, *_): for selected_database in self._databases.selected: url = '{0}://{1}:{2}/_utils/database.html?{3}'.format( 'https' if self.secure else 'http', self.server, self.port, selected_database.db_name) webbrowser.open_new_tab(url) def on_menu_databases_browse_fauxton(self, *_): url_format = '{0}://{1}:{2}/'.format('https' if self.secure else 'http', self.server, self.port) url_format += '_utils/fauxton/index.html#/database/{0}/_all_docs?limit=20' \ if self._model.couchdb.db_type is not CouchDB.DatabaseType.PouchDB else \ '_utils/#/database/{0}/_all_docs' for selected_database in self._databases.selected: url = url_format.format(selected_database.db_name) webbrowser.open_new_tab(url) def on_menu_databases_browse_alldocs(self, *_): for selected_database in self._databases.selected: url = '{0}://{1}:{2}/{3}/_all_docs?limit=100'.format( 'https' if self.secure else 'http', self.server, self.port, selected_database.db_name) webbrowser.open_new_tab(url) def on_menuitem_databases_replication_new(self, menu): replications = None selected_databases = self._databases.selected selected_count = len(selected_databases) if selected_count == 1: db = selected_databases[0] result = self.new_single_replication_dialog.run(self._model, db.db_name) if result == Gtk.ResponseType.OK: replications = self.new_single_replication_dialog.replications elif selected_count > 1: source_names = [db.db_name for db in selected_databases] result = self.new_multiple_replication_dialog.run(self._model, source_names) if result == Gtk.ResponseType.OK: replications = self.new_multiple_replication_dialog.replications if replications: self.checkmenuitem_view_new_replication_window.set_active(True) for repl in replications: self.queue_replication(repl) def on_menuitem_databases_replication_remote(self, *_): replications = None result = self.remote_replication_dialog.run(self._model) if result == Gtk.ResponseType.OK: replications = self.remote_replication_dialog.replications if replications: self.checkmenuitem_view_new_replication_window.set_active(True) for repl in replications: self.queue_replication(repl) def on_menu_databases_show(self, *_): connected = self._model is not None db_type = self._model.couchdb.db_type if connected else CouchDB.DatabaseType.Unknown is_pouchdb = db_type == CouchDB.DatabaseType.PouchDB is_cloudant = db_type == CouchDB.DatabaseType.Cloudant selected_databases = self._databases.selected single_row = len(selected_databases) == 1 multiple_rows = len(selected_databases) > 1 enable_backup = (single_row and selected_databases[0].db_name.find('backup$') < 0) or multiple_rows enable_restore = single_row and selected_databases[0].db_name.find('backup$') == 0 self.menuitem_databases_new.set_sensitive(connected) self.menuitem_databases_refresh.set_sensitive(connected) self.menuitem_databases_backup.set_sensitive(not is_pouchdb and enable_backup) self.menuitem_databases_restore.set_sensitive(not is_pouchdb and enable_restore) self.menuitem_databases_browse_futon.set_sensitive(not is_pouchdb and (single_row or multiple_rows)) self.menuitem_databases_browse_fauxton.set_sensitive(single_row or multiple_rows) self.menuitem_databases_browse_alldocs.set_sensitive(single_row or multiple_rows) self.menuitem_databases_delete.set_sensitive(single_row or multiple_rows) self.menuitem_databases_compact.set_sensitive(not is_cloudant and (single_row or multiple_rows)) self.menuitem_databases_replication_new.set_sensitive(single_row or multiple_rows) self.menuitem_databases_replication_from_remote.set_sensitive(connected) self.menuitem_database_set_revisions_1.set_sensitive(not is_pouchdb and (single_row or multiple_rows)) self.menuitem_database_set_revisions_10.set_sensitive(not is_pouchdb and (single_row or multiple_rows)) self.menuitem_database_set_revisions_100.set_sensitive(not is_pouchdb and (single_row or multiple_rows)) self.menuitem_database_set_revisions_1000.set_sensitive(not is_pouchdb and (single_row or multiple_rows)) def on_menu_databases_realize(self, menu): self.on_menu_databases_show(menu) def on_auto_update(self, *_): self._auto_update = self.checkbuttonAutoUpdate.get_active() def on_delete(self, *_): self.close() def on_checkmenuitem_view_new_replication_window_toggled(self, *_): active = self.checkmenuitem_view_new_replication_window.get_active() if active: self._new_replications_window.show() else: self._new_replications_window.hide() def on_hide_new_replication_window(self): self.checkmenuitem_view_new_replication_window.set_active(False) def on_imagemenuitem_file_quit(self, *_): self.close() def on_treeview_databases_drag_data_received(self, widget, drag_context, x, y, data, info, time): if self._model and info == 0: repl_count = 0 this_url = self._model.couchdb.get_url() text = data.get_text() urls = text.split('\n') for url in urls: if re.search('^https?://', url) is not None and url.find(this_url) != 0: u = urlparse(url) if not (u.hostname == self.server and u.port == self.port): target = u.path[1::] repl = Replication(self._model, url, target, continuous=False, create=True) self.queue_replication(repl) repl_count += 1 if repl_count: self.checkmenuitem_view_new_replication_window.set_active(True) def on_treeview_databases_drag_data_get(self, widget, drag_context, data, info, time): selected_databases = self._databases.selected selected_count = len(selected_databases) if selected_count > 0: text = '' url = self._model.url for db in selected_databases: if len(text) > 0: text += '\n' text += url + db.db_name data.set_text(text, -1) def on_menuitem_help_about_activate(self, *_): self.about_dialog.run() def on_menuitem_database_set_revisions_1_activate(self, *_): self.set_selected_databases_limit(1) def on_menuitem_database_set_revisions_10_activate(self, *_): self.set_selected_databases_limit(10) def on_menuitem_database_set_revisions_100_activate(self, *_): self.set_selected_databases_limit(100) def on_menuitem_database_set_revisions_1000_activate(self, *_): self.set_selected_databases_limit(1000)