def download_file(ctx, filepath, output, version): """ Download project file at specified version. `project` needs to be a combination of namespace/project. If no version is given, the latest will be fetched. """ mc = ctx.obj["client"] if mc is None: return mp = MerginProject(os.getcwd()) project_path = mp.metadata["name"] try: job = download_file_async(mc, project_path, filepath, output, version) with click.progressbar(length=job.total_size) as bar: last_transferred_size = 0 while download_project_is_running(job): time.sleep(1 / 10) # 100ms new_transferred_size = job.transferred_size bar.update( new_transferred_size - last_transferred_size) # the update() needs increment only last_transferred_size = new_transferred_size download_file_finalize(job) click.echo("Done") except KeyboardInterrupt: click.secho("Cancelling...") download_project_cancel(job) except ClientError as e: click.secho("Error: " + str(e), fg="red") except Exception as e: _print_unhandled_exception()
def show_file_changeset(ctx, path, version): """ Displays information about project changes.""" mc = ctx.obj["client"] if mc is None: return directory = os.getcwd() mp = MerginProject(directory) project_path = mp.metadata["name"] info_dict = mc.project_file_changeset_info(project_path, path, version) # TODO: handle exception if Diff not found click.secho(json.dumps(info_dict, indent=2))
def show_file_changeset(path, version): """ Displays information about a single version of a project """ c = _init_client() if c is None: return directory = os.getcwd() mp = MerginProject(directory) project_path = mp.metadata["name"] info_dict = c.project_file_changeset_info(project_path, path, version) print(json.dumps(info_dict, indent=2))
def show_version(version): """ Displays information about a single version of a project """ c = _init_client() if c is None: return directory = os.getcwd() mp = MerginProject(directory) project_path = mp.metadata["name"] version_info_dict = c.project_version_info(project_path, version)[0] print("Project: " + version_info_dict['project']['namespace'] + "/" + version_info_dict['project']['name']) print("Version: " + version_info_dict['name'] + " by " + version_info_dict['author']) print("Time: " + version_info_dict['created']) pretty_diff(version_info_dict['changes'])
def show_version(ctx, version): """ Displays information about a single version of a project. `version` is 'v1', 'v2', etc. """ mc = ctx.obj["client"] if mc is None: return directory = os.getcwd() mp = MerginProject(directory) project_path = mp.metadata["name"] # TODO: handle exception when version not found version_info_dict = mc.project_version_info(project_path, version)[0] click.secho("Project: " + version_info_dict["project"]["namespace"] + "/" + version_info_dict["project"]["name"]) click.secho("Version: " + version_info_dict["name"] + " by " + version_info_dict["author"]) click.secho("Time: " + version_info_dict["created"]) pretty_diff(version_info_dict["changes"])
def show_file_history(ctx, path): """ Displays information about a single version of a project """ mc = ctx.obj["client"] if mc is None: return directory = os.getcwd() mp = MerginProject(directory) project_path = mp.metadata["name"] info_dict = mc.project_file_history_info(project_path, path) # TODO: handle exception if history not found history_dict = info_dict["history"] click.secho("File history: " + info_dict["path"]) click.secho("-----") for version, version_data in history_dict.items(): diff_info = "" if "diff" in version_data: diff_info = "diff ({} bytes)".format(version_data["diff"]["size"]) click.secho(" {:5} {:10} {}".format(version, version_data["change"], diff_info))
def show_file_history(path): """ Displays information about a single version of a project """ c = _init_client() if c is None: return directory = os.getcwd() mp = MerginProject(directory) project_path = mp.metadata["name"] info_dict = c.project_file_history_info(project_path, path) history_dict = info_dict['history'] print("File history: " + info_dict['path']) print("-----") for version, version_data in history_dict.items(): diff_info = '' if 'diff' in version_data: diff_info = "diff ({} bytes)".format(version_data['diff']['size']) print(" {:5} {:10} {}".format(version, version_data['change'], diff_info))
def dbsync_push(mc): """ Take changes in the 'modified' schema in the database and push them to Mergin """ tmp_dir = tempfile.gettempdir() tmp_changeset_file = os.path.join(tmp_dir, 'dbsync-push-base2our') if os.path.exists(tmp_changeset_file): os.remove(tmp_changeset_file) _check_has_working_dir() _check_has_sync_file() mp = MerginProject(config.project_working_dir) if mp.geodiff is None: raise DbSyncError( "Mergin client installation problem: geodiff not available") project_path = mp.metadata["name"] local_version = mp.metadata["version"] try: projects = mc.get_projects_by_names([project_path]) server_version = projects[project_path]["version"] except ClientError as e: # this could be e.g. DNS error raise DbSyncError("Mergin client error: " + str(e)) status_push = mp.get_push_changes() if status_push['added'] or status_push['updated'] or status_push['removed']: raise DbSyncError( "There are pending changes in the local directory - that should never happen! " + str(status_push)) # check there are no pending changes on server if server_version != local_version: raise DbSyncError( "There are pending changes on server - need to pull them first.") conn = psycopg2.connect(config.db_conn_info) if not _check_schema_exists(conn, config.db_schema_base): raise DbSyncError("The base schema does not exist: " + config.db_schema_base) if not _check_schema_exists(conn, config.db_schema_modified): raise DbSyncError("The 'modified' schema does not exist: " + config.db_schema_modified) # get changes in the DB _geodiff_create_changeset(config.db_driver, config.db_conn_info, config.db_schema_base, config.db_schema_modified, tmp_changeset_file) if os.path.getsize(tmp_changeset_file) == 0: print("No changes in the database.") return # summarize changes summary = _geodiff_list_changes_summary(tmp_changeset_file) _print_changes_summary(summary) # write changes to the local geopackage print("Writing DB changes to working dir...") gpkg_full_path = os.path.join(config.project_working_dir, config.mergin_sync_file) _geodiff_apply_changeset("sqlite", "", gpkg_full_path, tmp_changeset_file) # write to the server try: mc.push_project(config.project_working_dir) except ClientError as e: # TODO: should we do some cleanup here? (undo changes in the local geopackage?) raise DbSyncError("Mergin client error on push: " + str(e)) version = _get_project_version() print("Pushed new version to Mergin: " + version) # update base schema in the DB print("Updating DB base schema...") _geodiff_apply_changeset(config.db_driver, config.db_conn_info, config.db_schema_base, tmp_changeset_file) _set_db_project_comment(conn, config.db_schema_base, config.mergin_project_name, version) print("Push done!")
def dbsync_status(mc): """ Figure out if there are any pending changes in the database or in Mergin """ _check_has_working_dir() _check_has_sync_file() # get basic information mp = MerginProject(config.project_working_dir) if mp.geodiff is None: raise DbSyncError( "Mergin client installation problem: geodiff not available") status_push = mp.get_push_changes() if status_push['added'] or status_push['updated'] or status_push['removed']: raise DbSyncError( "Pending changes in the local directory - that should never happen! " + str(status_push)) project_path = mp.metadata["name"] local_version = mp.metadata["version"] print("Working directory " + config.project_working_dir) print("Mergin project " + project_path + " at local version " + local_version) print("") print("Checking status...") # check if there are any pending changes on server try: server_info = mc.project_info(project_path, since=local_version) except ClientError as e: raise DbSyncError("Mergin client error: " + str(e)) print("Server is at version " + server_info["version"]) status_pull = mp.get_pull_changes(server_info["files"]) if status_pull['added'] or status_pull['updated'] or status_pull['removed']: print("There are pending changes on server:") _print_mergin_changes(status_pull) else: print("No pending changes on server.") print("") conn = psycopg2.connect(config.db_conn_info) if not _check_schema_exists(conn, config.db_schema_base): raise DbSyncError("The base schema does not exist: " + config.db_schema_base) if not _check_schema_exists(conn, config.db_schema_modified): raise DbSyncError("The 'modified' schema does not exist: " + config.db_schema_modified) # get changes in the DB tmp_dir = tempfile.gettempdir() tmp_changeset_file = os.path.join(tmp_dir, 'dbsync-status-base2our') if os.path.exists(tmp_changeset_file): os.remove(tmp_changeset_file) _geodiff_create_changeset(config.db_driver, config.db_conn_info, config.db_schema_base, config.db_schema_modified, tmp_changeset_file) if os.path.getsize(tmp_changeset_file) == 0: print("No changes in the database.") else: print("There are changes in DB") # summarize changes summary = _geodiff_list_changes_summary(tmp_changeset_file) _print_changes_summary(summary)
def dbsync_pull(mc): """ Downloads any changes from Mergin and applies them to the database """ _check_has_working_dir() _check_has_sync_file() mp = MerginProject(config.project_working_dir) if mp.geodiff is None: raise DbSyncError( "Mergin client installation problem: geodiff not available") project_path = mp.metadata["name"] local_version = mp.metadata["version"] try: projects = mc.get_projects_by_names([project_path]) server_version = projects[project_path]["version"] except ClientError as e: # this could be e.g. DNS error raise DbSyncError("Mergin client error: " + str(e)) status_push = mp.get_push_changes() if status_push['added'] or status_push['updated'] or status_push['removed']: raise DbSyncError( "There are pending changes in the local directory - that should never happen! " + str(status_push)) if server_version == local_version: print("No changes on Mergin.") return gpkg_basefile = os.path.join(config.project_working_dir, '.mergin', config.mergin_sync_file) gpkg_basefile_old = gpkg_basefile + "-old" # make a copy of the basefile in the current version (base) - because after pull it will be set to "their" shutil.copy(gpkg_basefile, gpkg_basefile_old) tmp_dir = tempfile.gettempdir() tmp_base2our = os.path.join(tmp_dir, 'dbsync-pull-base2our') tmp_base2their = os.path.join(tmp_dir, 'dbsync-pull-base2their') # find out our local changes in the database (base2our) _geodiff_create_changeset(config.db_driver, config.db_conn_info, config.db_schema_base, config.db_schema_modified, tmp_base2our) needs_rebase = False if os.path.getsize(tmp_base2our) != 0: needs_rebase = True summary = _geodiff_list_changes_summary(tmp_base2our) _print_changes_summary(summary, "DB Changes:") try: mc.pull_project(config.project_working_dir) # will do rebase as needed except ClientError as e: # TODO: do we need some cleanup here? raise DbSyncError("Mergin client error on pull: " + str(e)) print("Pulled new version from Mergin: " + _get_project_version()) # simple case when there are no pending local changes - just apply whatever changes are coming _geodiff_create_changeset("sqlite", "", gpkg_basefile_old, gpkg_basefile, tmp_base2their) # summarize changes summary = _geodiff_list_changes_summary(tmp_base2their) _print_changes_summary(summary, "Mergin Changes:") if not needs_rebase: print("Applying new version [no rebase]") _geodiff_apply_changeset(config.db_driver, config.db_conn_info, config.db_schema_base, tmp_base2their) _geodiff_apply_changeset(config.db_driver, config.db_conn_info, config.db_schema_modified, tmp_base2their) else: print("Applying new version [WITH rebase]") tmp_conflicts = os.path.join(tmp_dir, 'dbsync-pull-conflicts') _geodiff_rebase(config.db_driver, config.db_conn_info, config.db_schema_base, config.db_schema_modified, tmp_base2their, tmp_conflicts) _geodiff_apply_changeset(config.db_driver, config.db_conn_info, config.db_schema_base, tmp_base2their) os.remove(gpkg_basefile_old) conn = psycopg2.connect(config.db_conn_info) version = _get_project_version() _set_db_project_comment(conn, config.db_schema_base, config.mergin_project_name, version) print("Pull done!")
def _get_project_version(): """ Returns the current version of the project """ mp = MerginProject(config.project_working_dir) return mp.metadata["version"]