Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 62 additions & 11 deletions dbsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import sys
import tempfile
import random
import uuid

import psycopg2
from itertools import chain
Expand Down Expand Up @@ -199,14 +200,25 @@ 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
"""
comment = {
"name": project_name,
"version": version,
}
if project_id:
comment["project_id"] = project_id
if error:
comment["error"] = error
cur = conn.cursor()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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"]

Expand Down Expand Up @@ -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:")
Expand Down Expand Up @@ -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"]

Expand Down Expand Up @@ -560,9 +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:
# Get project ID from DB if available
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:
Expand All @@ -579,6 +627,9 @@ 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)
# 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)
Expand Down
25 changes: 24 additions & 1 deletion test/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import uuid

import pytest
import os
Expand All @@ -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')
Expand Down Expand Up @@ -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)