Skip to content
Snippets Groups Projects
synapse_port_db 41.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • Erik Johnston's avatar
    Erik Johnston committed
            self.stdscr = stdscr
    
            curses.use_default_colors()
            curses.curs_set(0)
    
            curses.init_pair(1, curses.COLOR_RED, -1)
            curses.init_pair(2, curses.COLOR_GREEN, -1)
    
            self.last_update = 0
    
            self.finished = False
    
    
            self.total_processed = 0
            self.total_remaining = 0
    
    
    Erik Johnston's avatar
    Erik Johnston committed
            super(CursesProgress, self).__init__()
    
        def update(self, table, num_done):
            super(CursesProgress, self).update(table, num_done)
    
    
            self.total_processed = 0
            self.total_remaining = 0
            for table, data in self.tables.items():
                self.total_processed += data["num_done"] - data["start"]
                self.total_remaining += data["total"] - data["num_done"]
    
    
    Erik Johnston's avatar
    Erik Johnston committed
            self.render()
    
        def render(self, force=False):
            now = time.time()
    
            if not force and now - self.last_update < 0.2:
                # reactor.callLater(1, self.render)
                return
    
            self.stdscr.clear()
    
            rows, cols = self.stdscr.getmaxyx()
    
            duration = int(now) - int(self.start_time)
    
            minutes, seconds = divmod(duration, 60)
    
            duration_str = "%02dm %02ds" % (minutes, seconds)
    
    Erik Johnston's avatar
    Erik Johnston committed
    
            if self.finished:
                status = "Time spent: %s (Done!)" % (duration_str,)
            else:
    
    
                if self.total_processed > 0:
                    left = float(self.total_remaining) / self.total_processed
    
                    est_remaining = (int(now) - self.start_time) * left
    
                    est_remaining_str = "%02dm %02ds remaining" % divmod(est_remaining, 60)
    
    Erik Johnston's avatar
    Erik Johnston committed
                else:
                    est_remaining_str = "Unknown"
    
                status = "Time spent: %s (est. remaining: %s)" % (
                    duration_str,
                    est_remaining_str,
    
            self.stdscr.addstr(0, 0, status, curses.A_BOLD)
    
    Erik Johnston's avatar
    Erik Johnston committed
    
            max_len = max([len(t) for t in self.tables.keys()])
    
            left_margin = 5
            middle_space = 1
    
            items = self.tables.items()
    
    Anders's avatar
    Anders committed
            items = sorted(items, key=lambda i: (i[1]["perc"], i[0]))
    
    Erik Johnston's avatar
    Erik Johnston committed
    
            for i, (table, data) in enumerate(items):
                if i + 2 >= rows:
                    break
    
                perc = data["perc"]
    
                color = curses.color_pair(2) if perc == 100 else curses.color_pair(1)
    
                self.stdscr.addstr(
    
                    i + 2, left_margin + max_len - len(table), table, curses.A_BOLD | color
    
    Erik Johnston's avatar
    Erik Johnston committed
                )
    
                size = 20
    
                progress = "[%s%s]" % (
    
                    "#" * int(perc * size / 100),
                    " " * (size - int(perc * size / 100)),
    
    Erik Johnston's avatar
    Erik Johnston committed
                )
    
                self.stdscr.addstr(
    
    Erik Johnston's avatar
    Erik Johnston committed
                    "%s %3d%% (%d/%d)" % (progress, perc, data["num_done"], data["total"]),
                )
    
            if self.finished:
    
                self.stdscr.addstr(rows - 1, 0, "Press any key to exit...")
    
    Erik Johnston's avatar
    Erik Johnston committed
    
            self.stdscr.refresh()
            self.last_update = time.time()
    
        def done(self):
            self.finished = True
            self.render(True)
            self.stdscr.getch()
    
        def set_state(self, state):
            self.stdscr.clear()
    
            self.stdscr.addstr(0, 0, state + "...", curses.A_BOLD)
    
    Erik Johnston's avatar
    Erik Johnston committed
            self.stdscr.refresh()
    
    
    class TerminalProgress(Progress):
        """Just prints progress to the terminal
        """
    
    Erik Johnston's avatar
    Erik Johnston committed
        def update(self, table, num_done):
            super(TerminalProgress, self).update(table, num_done)
    
            data = self.tables[table]
    
    
            print(
                "%s: %d%% (%d/%d)" % (table, data["perc"], data["num_done"], data["total"])
    
    Erik Johnston's avatar
    Erik Johnston committed
            )
    
        def set_state(self, state):
    
    Erik Johnston's avatar
    Erik Johnston committed
    
    
    ##############################################
    ##############################################
    
    
    
    if __name__ == "__main__":
    
        parser = argparse.ArgumentParser(
            description="A script to port an existing synapse SQLite database to"
    
        parser.add_argument("-v", action="store_true")
    
            help="The snapshot of the SQLite database file. This must not be"
    
            required=True,
            help="The database config file for the PostgreSQL database",
    
            "--curses", action="store_true", help="display a curses based progress UI"
    
            help="The number of rows to select from the SQLite table each"
    
        args = parser.parse_args()
    
    
        logging_config = {
            "level": logging.DEBUG if args.v else logging.INFO,
    
            "format": "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s",
    
        }
    
        if args.curses:
            logging_config["filename"] = "port-synapse.log"
    
        logging.basicConfig(**logging_config)
    
    
        sqlite_config = {
            "name": "sqlite3",
            "args": {
                "database": args.sqlite_database,
                "cp_min": 1,
                "cp_max": 1,
                "check_same_thread": False,
            },
        }
    
    
        hs_config = yaml.safe_load(args.postgres_config)
    
        if "database" not in hs_config:
            sys.stderr.write("The configuration file must have a 'database' section.\n")
            sys.exit(4)
    
        postgres_config = hs_config["database"]
    
        if "name" not in postgres_config:
    
            sys.stderr.write("Malformed database config: no 'name'\n")
    
            sys.exit(2)
        if postgres_config["name"] != "psycopg2":
    
            sys.stderr.write("Database must use the 'psycopg2' connector.\n")
    
        config = HomeServerConfig()
        config.parse_config_dict(hs_config, "", "")
    
    
        def start(stdscr=None):
            if stdscr:
                progress = CursesProgress(stdscr)
            else:
                progress = TerminalProgress()
    
            porter = Porter(
                sqlite_config=sqlite_config,
                progress=progress,
                batch_size=args.batch_size,
    
            @defer.inlineCallbacks
            def run():
                with LoggingContext("synapse_port_db_run"):
                    yield defer.ensureDeferred(porter.run())
    
            reactor.callWhenRunning(run)
    
        if end_error:
            if end_error_exec_info:
                exc_type, exc_value, exc_traceback = end_error_exec_info
                traceback.print_exception(exc_type, exc_value, exc_traceback)
    
            sys.stderr.write(end_error)