From 208ab3c690d25207e0431dd30dbd2b4efbb7fb54 Mon Sep 17 00:00:00 2001 From: ldebek Date: Thu, 5 Jan 2023 16:44:01 +0100 Subject: [PATCH 1/2] Added logic to use project ID to check that the project is the same. --- dbsync.py | 74 +++++++++++++++++++++++++++++++++++++++------- test/test_basic.py | 25 +++++++++++++++- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/dbsync.py b/dbsync.py index 46058a4..ce2c75e 100644 --- a/dbsync.py +++ b/dbsync.py @@ -15,6 +15,7 @@ import sys import tempfile import random +import uuid import psycopg2 from itertools import chain @@ -199,7 +200,16 @@ def _get_project_version(work_path): return mp.metadata["version"] -def _set_db_project_comment(conn, schema, project_name, version, error=None): +def _get_project_id(mp): + """ Returns the project ID """ + try: + project_id = uuid.UUID(mp.metadata["project_id"]) + except (KeyError, ValueError): + project_id = None + return project_id + + +def _set_db_project_comment(conn, schema, project_name, version, project_id=None, error=None): """ Set postgres COMMENT on SCHEMA with Mergin Maps project name and version or eventually error message if initialisation failed """ @@ -207,6 +217,8 @@ def _set_db_project_comment(conn, schema, project_name, version, error=None): "name": project_name, "version": version, } + if project_id: + comment["project_id"] = project_id if error: comment["error"] = error cur = conn.cursor() @@ -238,6 +250,25 @@ def _redownload_project(conn_cfg, mc, work_dir, db_proj_info): raise DbSyncError("Mergin Maps client error: " + str(e)) +def _validate_local_project_id(mp, mc, server_info=None): + """Compare local project ID with remote version on the server.""" + local_project_id = _get_project_id(mp) + if local_project_id is None: + return + project_path = mp.metadata["name"] + if server_info is None: + try: + server_info = mc.project_info(project_path) + except ClientError as e: + raise DbSyncError("Mergin Maps client error: " + str(e)) + + remote_project_id = uuid.UUID(server_info["id"]) + if local_project_id != remote_project_id: + raise DbSyncError( + f"The local project ID ({local_project_id}) does not match the server project ID ({remote_project_id})" + ) + + def create_mergin_client(): """ Create instance of MerginClient""" _check_has_password() @@ -301,6 +332,10 @@ def pull(conn_cfg, mc): mp.set_tables_to_skip(ignored_tables) if mp.geodiff is None: raise DbSyncError("Mergin Maps client installation problem: geodiff not available") + + # Make sure that local project ID (if available) is the same as on the server + _validate_local_project_id(mp, mc) + project_path = mp.metadata["name"] local_version = mp.metadata["version"] @@ -390,25 +425,26 @@ def status(conn_cfg, mc): mp.set_tables_to_skip(ignored_tables) if mp.geodiff is None: raise DbSyncError("Mergin Maps 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 " + work_dir) - print("Mergin Maps 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 Maps client error: " + str(e)) - print("Server is at version " + server_info["version"]) + # Make sure that local project ID (if available) is the same as on the server + _validate_local_project_id(mp, mc, server_info) + 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)) + + print("Working directory " + work_dir) + print("Mergin Maps project " + project_path + " at local version " + local_version) + print("") + + 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:") @@ -462,6 +498,10 @@ def push(conn_cfg, mc): mp.set_tables_to_skip(ignored_tables) if mp.geodiff is None: raise DbSyncError("Mergin Maps client installation problem: geodiff not available") + + # Make sure that local project ID (if available) is the same as on the server + _validate_local_project_id(mp, mc) + project_path = mp.metadata["name"] local_version = mp.metadata["version"] @@ -560,6 +600,14 @@ def init(conn_cfg, mc, from_gpkg=True): f"to {work_dir}") mc.download_project(conn_cfg.mergin_project, work_dir, db_proj_info["version"]) else: + print(f"Working directory {work_dir} already exists, with project version {local_version}") + # Get project ID from DB if available + db_project_id = getattr(db_proj_info, "project_id", None) + mp = _get_mergin_project(work_dir) + local_project_id = _get_project_id(mp) + if (db_project_id and local_project_id) and (db_project_id != local_project_id): + raise DbSyncError(f"Database project ID doesn't match local project ID.") + # Compare local and database project version try: local_version = _get_project_version(work_dir) print(f"Working directory {work_dir} already exists, with project version {local_version}") @@ -579,6 +627,10 @@ def init(conn_cfg, mc, from_gpkg=True): # make sure we have working directory now _check_has_working_dir(work_dir) local_version = _get_project_version(work_dir) + mp = _get_mergin_project(work_dir) + print(mp.metadata) + # Make sure that local project ID (if available) is the same as on the server + _validate_local_project_id(mp, mc) # check there are no pending changes on server (or locally - which should never happen) status_pull, status_push, _ = mc.project_status(work_dir) diff --git a/test/test_basic.py b/test/test_basic.py index e259a6c..fe08890 100644 --- a/test/test_basic.py +++ b/test/test_basic.py @@ -1,3 +1,4 @@ +import uuid import pytest import os @@ -9,7 +10,7 @@ from mergin import MerginClient, ClientError from dbsync import dbsync_init, dbsync_pull, dbsync_push, dbsync_status, config, DbSyncError, _geodiff_make_copy, \ - _get_db_project_comment, _get_mergin_project, config + _get_db_project_comment, _get_mergin_project, _get_project_id, _validate_local_project_id, config GEODIFF_EXE = os.environ.get('TEST_GEODIFF_EXE') DB_CONNINFO = os.environ.get('TEST_DB_CONNINFO') @@ -409,3 +410,25 @@ def test_with_local_changes(mc): local_changes = mp.get_push_changes() assert any(local_changes.values()) is False dbsync_status(mc) + + +def test_recreated_project_ids(mc): + project_name = 'test_recreated_project_ids' + source_gpkg_path = os.path.join(TEST_DATA_DIR, 'base.gpkg') + project_dir = os.path.join(TMP_DIR, project_name + '_work') # working directory + full_project_name = API_USER + "/" + project_name + init_sync_from_geopackage(mc, project_name, source_gpkg_path) + # delete remote project + mc.delete_project(full_project_name) + # recreate project with the same name + mc.create_project(project_name) + # comparing project IDs after recreating it with the same name + mp = _get_mergin_project(project_dir) + local_project_id = _get_project_id(mp) + server_info = mc.project_info(full_project_name) + server_project_id = uuid.UUID(server_info["id"]) + assert local_project_id is not None + assert server_project_id is not None + assert local_project_id != server_project_id + with pytest.raises(DbSyncError): + dbsync_status(mc) From fee04ef35505f59aac776c7d75370219da7f5af3 Mon Sep 17 00:00:00 2001 From: ldebek Date: Thu, 5 Jan 2023 18:58:34 +0100 Subject: [PATCH 2/2] Added logic to use project ID to check that the project is the same. --- dbsync.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/dbsync.py b/dbsync.py index ce2c75e..51ccedf 100644 --- a/dbsync.py +++ b/dbsync.py @@ -600,17 +600,17 @@ def init(conn_cfg, mc, from_gpkg=True): f"to {work_dir}") mc.download_project(conn_cfg.mergin_project, work_dir, db_proj_info["version"]) else: - print(f"Working directory {work_dir} already exists, with project version {local_version}") # Get project ID from DB if available - db_project_id = getattr(db_proj_info, "project_id", None) - mp = _get_mergin_project(work_dir) - local_project_id = _get_project_id(mp) - if (db_project_id and local_project_id) and (db_project_id != local_project_id): - raise DbSyncError(f"Database project ID doesn't match local project ID.") - # Compare local and database project version try: local_version = _get_project_version(work_dir) print(f"Working directory {work_dir} already exists, with project version {local_version}") + # Compare local and database project version + db_project_id_str = getattr(db_proj_info, "project_id", None) + db_project_id = uuid.UUID(db_project_id_str) if db_project_id_str else None + mp = _get_mergin_project(work_dir) + local_project_id = _get_project_id(mp) + if (db_project_id and local_project_id) and (db_project_id != local_project_id): + raise DbSyncError(f"Database project ID doesn't match local project ID.") if local_version != db_proj_info["version"]: _redownload_project(conn_cfg, mc, work_dir, db_proj_info) except InvalidProject as e: @@ -628,7 +628,6 @@ def init(conn_cfg, mc, from_gpkg=True): _check_has_working_dir(work_dir) local_version = _get_project_version(work_dir) mp = _get_mergin_project(work_dir) - print(mp.metadata) # Make sure that local project ID (if available) is the same as on the server _validate_local_project_id(mp, mc)