Skip to content

Commit e36ae8d

Browse files
committed
address comments without progress bar @wonder-sk
1 parent 77b82e9 commit e36ae8d

File tree

5 files changed

+65
-48
lines changed

5 files changed

+65
-48
lines changed

mergin/client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,9 @@ def sync_project(self, project_directory, progress_callback=None):
15191519
2. Get local changes
15201520
3. Push first change batch
15211521
Repeat if there are more local changes.
1522+
1523+
:param project_directory: Project's directory
1524+
:param progress_callback: Optional callback function to report progress during push.
15221525
"""
15231526
mp = MerginProject(project_directory)
15241527
has_changes = True
@@ -1546,6 +1549,7 @@ def sync_project(self, project_directory, progress_callback=None):
15461549
last_size = current_size
15471550
push_project_finalize(job)
15481551
_, has_changes = get_push_changes_batch(self, mp)
1552+
server_conflict_attempts = 0
15491553
except ClientError as e:
15501554
if e.is_retryable_sync() and server_conflict_attempts < PUSH_ATTEMPTS - 1:
15511555
# retry on conflict, e.g. when server has changes that we do not have yet
@@ -1556,5 +1560,3 @@ def sync_project(self, project_directory, progress_callback=None):
15561560
sleep(PUSH_ATTEMPT_WAIT)
15571561
continue
15581562
raise e
1559-
else:
1560-
server_conflict_attempts = 0

mergin/client_push.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import time
2323
from typing import List, Tuple, Optional, ByteString
2424

25-
from .local_changes import LocalChange, LocalChanges
25+
from .local_changes import FileChange, LocalPojectChanges
2626

2727
from .common import UPLOAD_CHUNK_ATTEMPT_WAIT, UPLOAD_CHUNK_ATTEMPTS, UPLOAD_CHUNK_SIZE, ClientError, ErrorCode
2828
from .merginproject import MerginProject
@@ -66,7 +66,7 @@ def __init__(
6666

6767
self._request_headers = {"Content-Type": "application/octet-stream"}
6868

69-
def upload_chunk(self, data: ByteString, checksum: str):
69+
def upload_chunk_v1_api(self, data: ByteString, checksum: str):
7070
"""
7171
Uploads the chunk to the server.
7272
"""
@@ -118,7 +118,7 @@ def upload_blocking(self):
118118
self.upload_chunk_v2_api(data, checksum_str)
119119
else:
120120
# use v1 API for uploading chunks
121-
self.upload_chunk(data, checksum_str)
121+
self.upload_chunk_v1_api(data, checksum_str)
122122
break # exit loop if upload was successful
123123
except ClientError as e:
124124
if attempt < UPLOAD_CHUNK_ATTEMPTS - 1:
@@ -133,10 +133,10 @@ class UploadJob:
133133
"""Keeps all the important data about a pending upload job"""
134134

135135
def __init__(
136-
self, version: str, changes: LocalChanges, transaction_id: Optional[str], mp: MerginProject, mc, tmp_dir
136+
self, version: str, changes: LocalPojectChanges, transaction_id: Optional[str], mp: MerginProject, mc, tmp_dir
137137
):
138138
self.version = version
139-
self.changes: LocalChanges = changes # dictionary of local changes to the project
139+
self.changes: LocalPojectChanges = changes # dictionary of local changes to the project
140140
self.transaction_id = transaction_id # ID of the transaction assigned by the server
141141
self.total_size = 0 # size of data to upload (in bytes)
142142
self.transferred_size = 0 # size of data already uploaded (in bytes)
@@ -160,8 +160,8 @@ def _submit_item_to_thread(self, item: UploadQueueItem):
160160
future = self.executor.submit(_do_upload, item, self)
161161
self.futures.append(future)
162162

163-
def add_items(self, items: List[UploadQueueItem]):
164-
"""Add multiple chunks to the upload queue"""
163+
def start(self, items: List[UploadQueueItem]):
164+
"""Starting upload in background with multiple upload items (UploadQueueItem)"""
165165
self.total_size = sum(item.size for item in items)
166166
self.upload_queue_items = items
167167

@@ -175,7 +175,10 @@ def add_items(self, items: List[UploadQueueItem]):
175175
self._submit_item_to_thread(item)
176176

177177
def update_chunks_from_items(self):
178-
"""Update chunks in LocalChanges from the upload queue items."""
178+
"""
179+
Update chunks in LocalChanges from the upload queue items.
180+
Used just before finalizing the transaction to set the server_chunk_id in v2 API.
181+
"""
179182
self.changes.update_chunks([(item.chunk_id, item.server_chunk_id) for item in self.upload_queue_items])
180183

181184

@@ -188,7 +191,7 @@ def _do_upload(item: UploadQueueItem, job: UploadJob):
188191
job.transferred_size += item.size
189192

190193

191-
def create_upload_chunks(mc, mp: MerginProject, local_changes: List[LocalChange]) -> List[UploadQueueItem]:
194+
def create_upload_chunks(mc, mp: MerginProject, local_changes: List[FileChange]) -> List[UploadQueueItem]:
192195
"""
193196
Create a list of UploadQueueItem objects from the changes dictionary and calculate total size of files.
194197
This is used to prepare the upload queue for the upload job.
@@ -221,7 +224,7 @@ def create_upload_chunks(mc, mp: MerginProject, local_changes: List[LocalChange]
221224

222225

223226
def create_upload_job(
224-
mc, mp: MerginProject, changes: LocalChanges, tmp_dir: tempfile.TemporaryDirectory
227+
mc, mp: MerginProject, changes: LocalPojectChanges, tmp_dir: tempfile.TemporaryDirectory
225228
) -> Optional[UploadJob]:
226229
"""
227230
Prepare transaction and create an upload job for the project using the v1 API.
@@ -261,18 +264,20 @@ def create_upload_job(
261264
if not upload_changes:
262265
mp.log.info("not uploading any files")
263266
if push_start_resp:
267+
# This is related just to v1 API
264268
job.server_resp = push_start_resp
265269
push_project_finalize(job)
266270
return # all done - no pending job
267271

268272
if transaction_id:
273+
# This is related just to v1 API
269274
mp.log.info(f"got transaction ID {transaction_id}")
270275

271276
# prepare file chunks for upload
272277
upload_queue_items = create_upload_chunks(mc, mp, upload_changes)
273278

274279
mp.log.info(f"Starting upload chunks for project {project_id}")
275-
job.add_items(upload_queue_items)
280+
job.start(upload_queue_items)
276281
return job
277282

278283

@@ -312,10 +317,10 @@ def push_project_async(mc, directory) -> Optional[UploadJob]:
312317
if mp.is_versioned_file(f["path"]):
313318
mp.copy_versioned_file_for_upload(f, tmp_dir.name)
314319

315-
local_changes = LocalChanges(
316-
added=[LocalChange(**change) for change in changes["added"]],
317-
updated=[LocalChange(**change) for change in changes["updated"]],
318-
removed=[LocalChange(**change) for change in changes["removed"]],
320+
local_changes = LocalPojectChanges(
321+
added=[FileChange(**change) for change in changes["added"]],
322+
updated=[FileChange(**change) for change in changes["updated"]],
323+
removed=[FileChange(**change) for change in changes["removed"]],
319324
)
320325
job = create_upload_job(mc, mp, local_changes, tmp_dir)
321326
return job
@@ -449,6 +454,7 @@ def push_project_cancel(job: UploadJob):
449454

450455
job.executor.shutdown(wait=True)
451456
if not job.transaction_id:
457+
# If not v2 api endpoint with transaction, nothing to cancel on server
452458
job.mp.log.info("--- push cancelled")
453459
return
454460
try:

mergin/editor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def prevent_conflicted_copy(path: str, mc, project_role: str) -> bool:
6464
Args:
6565
path (str): The file path to check.
6666
mc: The Mergin client object.
67-
project_info (dict): Information about the Mergin project from server.
67+
project_role: Current project role.
6868
6969
Returns:
7070
bool: True if the file path should be prevented from ceating conflicted copy, False otherwise.

mergin/local_changes.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,28 @@
44

55

66
@dataclass
7-
class BaseLocalChange:
7+
class FileDiffChange:
88
path: str
99
checksum: str
1010
size: int
1111
mtime: datetime
1212

1313

1414
@dataclass
15-
class LocalChange(BaseLocalChange):
15+
class FileChange:
16+
# path to the file relative to the project root
17+
path: str
18+
# file checksum
19+
checksum: str
20+
# file size in bytes
21+
size: int
22+
# file modification time
23+
mtime: datetime
24+
# original file checksum used for compairison
1625
origin_checksum: Optional[str] = None
26+
# list of chunk ids that make up this file
1727
chunks: List[str] = field(default_factory=list)
28+
# optional diff information for geopackage files with geodiff metadata
1829
diff: Optional[dict] = None
1930
upload_file: Optional[str] = None
2031
# some functions (MerginProject.compare_file_sets) are adding version to the change from project info
@@ -24,9 +35,9 @@ class LocalChange(BaseLocalChange):
2435
# some functions (MerginProject.compare_file_sets) are adding location dict to the change from project info
2536
location: Optional[str] = None
2637

27-
def get_diff(self) -> Optional[BaseLocalChange]:
38+
def get_diff(self) -> Optional[FileDiffChange]:
2839
if self.diff:
29-
return BaseLocalChange(
40+
return FileDiffChange(
3041
path=self.diff.get("path", ""),
3142
checksum=self.diff.get("checksum", ""),
3243
size=self.diff.get("size", 0),
@@ -50,10 +61,10 @@ def to_server_data(self) -> dict:
5061

5162

5263
@dataclass
53-
class LocalChanges:
54-
added: List[LocalChange] = field(default_factory=list)
55-
updated: List[LocalChange] = field(default_factory=list)
56-
removed: List[LocalChange] = field(default_factory=list)
64+
class LocalPojectChanges:
65+
added: List[FileChange] = field(default_factory=list)
66+
updated: List[FileChange] = field(default_factory=list)
67+
removed: List[FileChange] = field(default_factory=list)
5768

5869
def to_server_payload(self) -> dict:
5970
return {
@@ -62,7 +73,7 @@ def to_server_payload(self) -> dict:
6273
"removed": [change.to_server_data() for change in self.removed],
6374
}
6475

65-
def get_upload_changes(self) -> List[LocalChange]:
76+
def get_upload_changes(self) -> List[FileChange]:
6677
"""
6778
Get all changes that need to be uploaded.
6879
This includes added and updated files.
@@ -76,9 +87,7 @@ def _map_unique_chunks(self, change_chunks: List[str], server_chunks: List[Tuple
7687
mapped = []
7788
seen = set()
7889
for chunk in change_chunks:
79-
for server_chunk in server_chunks:
80-
chunk_id = server_chunk[0]
81-
server_chunk_id = server_chunk[1]
90+
for chunk_id, server_chunk_id in server_chunks:
8291
if chunk_id == chunk and server_chunk_id not in seen:
8392
mapped.append(server_chunk_id)
8493
seen.add(server_chunk_id)

mergin/test/test_local_changes.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import datetime
22

3-
from ..local_changes import LocalChange, LocalChanges
3+
from ..local_changes import FileChange, LocalPojectChanges
44

55

66
def test_local_changes_from_dict():
@@ -31,11 +31,11 @@ def test_local_changes_from_dict():
3131
}
3232

3333
# Convert dictionary to LocalChanges
34-
added = [LocalChange(**file) for file in changes_dict["added"]]
35-
updated = [LocalChange(**file) for file in changes_dict["updated"]]
36-
removed = [LocalChange(**file) for file in changes_dict["removed"]]
34+
added = [FileChange(**file) for file in changes_dict["added"]]
35+
updated = [FileChange(**file) for file in changes_dict["updated"]]
36+
removed = [FileChange(**file) for file in changes_dict["removed"]]
3737

38-
local_changes = LocalChanges(added=added, updated=updated, removed=removed)
38+
local_changes = LocalPojectChanges(added=added, updated=updated, removed=removed)
3939

4040
# Assertions
4141
assert len(local_changes.added) == 1
@@ -51,7 +51,7 @@ def test_local_changes_from_dict():
5151

5252
def test_local_change_get_diff():
5353
"""Test the get_diff method of LocalChange."""
54-
local_change = LocalChange(
54+
local_change = FileChange(
5555
path="file1.txt",
5656
checksum="abc123",
5757
size=1024,
@@ -68,11 +68,11 @@ def test_local_change_get_diff():
6868

6969
def test_local_changes_to_server_payload():
7070
"""Test the to_server_payload method of LocalChanges."""
71-
added = [LocalChange(path="file1.txt", checksum="abc123", size=1024, mtime=datetime.now())]
72-
updated = [LocalChange(path="file2.txt", checksum="xyz789", size=2048, mtime=datetime.now())]
73-
removed = [LocalChange(path="file3.txt", checksum="lmn456", size=512, mtime=datetime.now())]
71+
added = [FileChange(path="file1.txt", checksum="abc123", size=1024, mtime=datetime.now())]
72+
updated = [FileChange(path="file2.txt", checksum="xyz789", size=2048, mtime=datetime.now())]
73+
removed = [FileChange(path="file3.txt", checksum="lmn456", size=512, mtime=datetime.now())]
7474

75-
local_changes = LocalChanges(added=added, updated=updated, removed=removed)
75+
local_changes = LocalPojectChanges(added=added, updated=updated, removed=removed)
7676
server_request = local_changes.to_server_payload()
7777

7878
assert "added" in server_request
@@ -86,12 +86,12 @@ def test_local_changes_to_server_payload():
8686
def test_local_changes_update_chunks():
8787
"""Test the update_chunks method of LocalChanges."""
8888
added = [
89-
LocalChange(path="file1.txt", checksum="abc123", size=1024, mtime=datetime.now(), chunks=["abc123"]),
90-
LocalChange(path="file2.txt", checksum="abc123", size=1024, mtime=datetime.now(), chunks=["abc123"]),
89+
FileChange(path="file1.txt", checksum="abc123", size=1024, mtime=datetime.now(), chunks=["abc123"]),
90+
FileChange(path="file2.txt", checksum="abc123", size=1024, mtime=datetime.now(), chunks=["abc123"]),
9191
]
92-
updated = [LocalChange(path="file2.txt", checksum="xyz789", size=2048, mtime=datetime.now(), chunks=["xyz789"])]
92+
updated = [FileChange(path="file2.txt", checksum="xyz789", size=2048, mtime=datetime.now(), chunks=["xyz789"])]
9393

94-
local_changes = LocalChanges(added=added, updated=updated)
94+
local_changes = LocalPojectChanges(added=added, updated=updated)
9595
chunks = [("abc123", "chunk1"), ("abc123", "chunk1"), ("xyz789", "chunk2")]
9696

9797
local_changes.update_chunks(chunks)
@@ -104,12 +104,12 @@ def test_local_changes_update_chunks():
104104
def test_local_changes_get_upload_changes():
105105
"""Test the get_upload_changes method of LocalChanges."""
106106
# Create sample LocalChange instances
107-
added = [LocalChange(path="file1.txt", checksum="abc123", size=1024, mtime=datetime.now())]
108-
updated = [LocalChange(path="file2.txt", checksum="xyz789", size=2048, mtime=datetime.now())]
109-
removed = [LocalChange(path="file3.txt", checksum="lmn456", size=512, mtime=datetime.now())]
107+
added = [FileChange(path="file1.txt", checksum="abc123", size=1024, mtime=datetime.now())]
108+
updated = [FileChange(path="file2.txt", checksum="xyz789", size=2048, mtime=datetime.now())]
109+
removed = [FileChange(path="file3.txt", checksum="lmn456", size=512, mtime=datetime.now())]
110110

111111
# Initialize LocalChanges with added, updated, and removed changes
112-
local_changes = LocalChanges(added=added, updated=updated, removed=removed)
112+
local_changes = LocalPojectChanges(added=added, updated=updated, removed=removed)
113113

114114
# Call get_upload_changes
115115
upload_changes = local_changes.get_upload_changes()

0 commit comments

Comments
 (0)