Skip to content

Commit 85fe64d

Browse files
committed
Cleanup and docstrings
1 parent baf1e32 commit 85fe64d

File tree

5 files changed

+97
-95
lines changed

5 files changed

+97
-95
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Commands:
8585
show-file-history Display information about a single version of a...
8686
show-version Display information about a single version of a...
8787
status Show all changes in project files - upstream and...
88-
sync Syncronize the project.
88+
sync Synchronize the project.
8989
```
9090

9191
### Examples

mergin/cli.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -411,23 +411,13 @@ def sync(ctx):
411411
return
412412
directory = os.getcwd()
413413

414-
size = total_upload_size(directory)
415-
if size == 0:
416-
click.secho("Already up to date.", fg="green")
417-
return
418-
419414
try:
420-
uploaded_so_far = 0
421-
422-
with click.progressbar(length=size, label="Uploading changes") as bar:
423-
# updates the progress bar
424-
def on_progress(last, now):
425-
nonlocal uploaded_so_far
426-
delta = now - last
427-
uploaded_so_far += delta
428-
bar.update(delta)
415+
size = total_upload_size(directory)
416+
with click.progressbar(length=size, label="Syncing") as bar:
417+
def on_progress(increment):
418+
bar.update(increment)
429419

430-
# run pull & push cycles
420+
# run pull & push cycles until there are no local changes
431421
mc.sync_project_with_callback(directory, progress_callback=on_progress)
432422

433423
click.secho("Sync complete.", fg="green")
@@ -439,8 +429,10 @@ def on_progress(last, now):
439429
return
440430
except KeyboardInterrupt:
441431
click.secho("Cancelling...")
442-
push_project_cancel(mc.upload_job)
443-
pull_project_cancel(mc.download_job)
432+
if mc.pull_job:
433+
pull_project_cancel(mc.pull_job)
434+
if mc.push_job:
435+
push_project_cancel(mc.push_job)
444436
except Exception as e:
445437
_print_unhandled_exception()
446438

mergin/client.py

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,12 @@
3333
download_project_finalize,
3434
download_project_wait,
3535
download_diffs_finalize,
36-
pull_project_is_running,
3736
pull_project_async,
3837
pull_project_wait,
3938
pull_project_finalize
4039
)
4140
from .client_push import push_project_wait, push_project_finalize, push_project_async, push_project_is_running, \
42-
ChangesHandler
41+
ChangesHandler, get_change_batch
4342
from .utils import DateTimeEncoder, get_versions_with_file_changes, int_version, is_version_acceptable
4443
from .version import __version__
4544

@@ -936,45 +935,48 @@ def sync_project(self, project_dir):
936935
has_more_changes = True
937936
while has_more_changes:
938937
self.pull_job = pull_project_async(self, project_dir)
939-
pull_project_wait(self.pull_job)
940-
pull_project_finalize(self.pull_job)
938+
if self.pull_job is not None:
939+
pull_project_wait(self.pull_job)
940+
pull_project_finalize(self.pull_job)
941941

942-
changes_handler = ChangesHandler(self, project_dir)
943-
changes_batch, has_more_changes = changes_handler.get_change_batch()
942+
changes_batch, has_more_changes = get_change_batch(self, project_dir)
943+
if not changes_batch: # no changes to upload, quit sync
944+
return
944945

945-
self.push_job = push_project_async(project_dir, changes_batch)
946-
push_project_wait(self.push_job)
947-
push_project_finalize(self.push_job)
946+
self.push_job = push_project_async(self, project_dir, changes_batch)
947+
if self.push_job:
948+
push_project_wait(self.push_job)
949+
push_project_finalize(self.push_job)
948950

949-
def sync_project_with_callback(self, project_dir, progress_callback=None, sleep_time=100):
951+
def sync_project_with_callback(self, project_dir, progress_callback=None, sleep_time=0.1):
950952
"""
951953
Syncs project while sending push progress info as callback.
952954
Sync is done in this loop:
953-
Pending changes? -> Pull -> Push change batch -> repeat
955+
Pending changes? -> Pull -> Get changes batch -> Push the changes -> repeat
956+
957+
:param progress_callback: updates the progress bar in CLI, on_progress(increment)
958+
:param sleep_time: sleep time between calling the callback function
954959
"""
955960
has_more_changes = True
956961
while has_more_changes:
957-
changes_handler = ChangesHandler(self, project_dir)
958-
959962
self.pull_job = pull_project_async(self, project_dir)
960-
if self.pull_job is None:
961-
return
962-
pull_project_wait(self.pull_job)
963-
pull_project_finalize(self.pull_job)
964-
965-
changes_batch, has_more_changes = changes_handler.get_change_batch()
963+
if self.pull_job is not None:
964+
pull_project_wait(self.pull_job)
965+
pull_project_finalize(self.pull_job)
966966

967-
self.push_job = push_project_async(self, project_dir, changes_batch)
968-
if not self.push_job:
967+
changes_batch, has_more_changes = get_change_batch(self, project_dir)
968+
if not changes_batch: # no changes to upload, quit sync
969969
return
970970

971-
last = 0
972-
while push_project_is_running(self.push_job):
973-
sleep(sleep_time)
974-
now = self.push_job.transferred_size
975-
progress_callback(last, now)
976-
last = now
977-
push_project_finalize(self.push_job)
971+
self.push_job = push_project_async(self, project_dir, changes_batch)
972+
if self.push_job:
973+
last = 0
974+
while push_project_is_running(self.push_job):
975+
sleep(sleep_time)
976+
now = self.push_job.transferred_size
977+
progress_callback(now - last) # update progressbar with transferred size increment
978+
last = now
979+
push_project_finalize(self.push_job)
978980

979981

980982
def clone_project(self, source_project_path, cloned_project_name, cloned_project_namespace=None):

mergin/client_push.py

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ def _split_by_type(self, changes: Dict[str, List[dict]]) -> List[Dict[str, List[
132132
1. Blocking: updated/removed and added files that are blocking
133133
2. Non-blocking: added files that are not blocking
134134
"""
135-
blocking_changes = {"added": [], "updated": [], "removed": [], "renamed": []}
136-
non_blocking_changes = {"added": [], "updated": [], "removed": [], "renamed": []}
135+
blocking_changes = {"added": [], "updated": [], "removed": []}
136+
non_blocking_changes = {"added": [], "updated": [], "removed": []}
137137

138138
for f in changes.get("added", []):
139139
if self.is_blocking_file(f):
@@ -164,18 +164,26 @@ def split(self) -> List[Dict[str, List[dict]]]:
164164
# TODO: apply limits; changes = self._limit_by_file_count(changes)
165165
return changes_list
166166

167-
def get_change_batch(self) -> Tuple[Optional[Dict[str, List[dict]]], bool]:
168-
"""
169-
Return the next changes dictionary and flag if there are more changes
170-
"""
171-
changes_list = self.split()
172-
if not changes_list:
173-
return None, False
174-
return changes_list[0], len(changes_list) > 1
167+
def get_change_batch(mc, project_dir) -> Tuple[Optional[Dict[str, List[dict]]], bool]:
168+
"""
169+
Return the next changes dictionary and flag if there are more changes (to be uploaded in the next upload job)
170+
"""
171+
changes_list = ChangesHandler(mc, project_dir).split()
172+
if not changes_list:
173+
return None, False
174+
non_empty_length = sum(any(v for v in d.values()) for d in changes_list)
175+
return changes_list[0], non_empty_length > 1
175176

176177

177178
def push_project_async(mc, directory, change_batch=None) -> Optional[UploadJob]:
178-
"""Starts push of a change of a project and returns pending upload job"""
179+
"""
180+
Starts push in background and returns pending upload job.
181+
Pushes all project changes unless change_batch is provided.
182+
When specific change is provided, initial version check is skipped (the pull has just been done).
183+
184+
:param change_batch: A dictionary of changes that was split to blocking and non-blocking.
185+
Pushing only non-blocking changes results in non-exclusive upload which server allows to be concurrent.
186+
"""
179187

180188
mp = MerginProject(directory)
181189
if mp.has_unfinished_pull():
@@ -187,30 +195,32 @@ def push_project_async(mc, directory, change_batch=None) -> Optional[UploadJob]:
187195
mp.log.info("--- version: " + mc.user_agent_info())
188196
mp.log.info(f"--- start push {project_path}")
189197

190-
try:
191-
project_info = mc.project_info(project_path)
192-
except ClientError as err:
193-
mp.log.error("Error getting project info: " + str(err))
194-
mp.log.info("--- push aborted")
195-
raise
196-
server_version = project_info["version"] if project_info["version"] else "v0"
197-
198-
mp.log.info(f"got project info: local version {local_version} / server version {server_version}")
199-
200-
username = mc.username()
201-
# permissions field contains information about update, delete and upload privileges of the user
202-
# on a specific project. This is more accurate information than "writernames" field, as it takes
203-
# into account namespace privileges. So we have to check only "permissions", namely "upload" once
204-
if not mc.has_writing_permissions(project_path):
205-
mp.log.error(f"--- push {project_path} - username {username} does not have write access")
206-
raise ClientError(f"You do not seem to have write access to the project (username '{username}')")
207-
208-
if local_version != server_version:
209-
mp.log.error(f"--- push {project_path} - not up to date (local {local_version} vs server {server_version})")
210-
raise ClientError(
211-
"There is a new version of the project on the server. Please update your local copy."
212-
+ f"\n\nLocal version: {local_version}\nServer version: {server_version}"
213-
)
198+
# if we have specific change to push we don't need version check
199+
if not change_batch:
200+
try:
201+
project_info = mc.project_info(project_path)
202+
except ClientError as err:
203+
mp.log.error("Error getting project info: " + str(err))
204+
mp.log.info("--- push aborted")
205+
raise
206+
server_version = project_info["version"] if project_info["version"] else "v0"
207+
208+
mp.log.info(f"got project info: local version {local_version} / server version {server_version}")
209+
210+
username = mc.username()
211+
# permissions field contains information about update, delete and upload privileges of the user
212+
# on a specific project. This is more accurate information than "writernames" field, as it takes
213+
# into account namespace privileges. So we have to check only "permissions", namely "upload" once
214+
if not mc.has_writing_permissions(project_path):
215+
mp.log.error(f"--- push {project_path} - username {username} does not have write access")
216+
raise ClientError(f"You do not seem to have write access to the project (username '{username}')")
217+
218+
if local_version != server_version:
219+
mp.log.error(f"--- push {project_path} - not up to date (local {local_version} vs server {server_version})")
220+
raise ClientError(
221+
"There is a new version of the project on the server. Please update your local copy."
222+
+ f"\n\nLocal version: {local_version}\nServer version: {server_version}"
223+
)
214224

215225
changes = change_batch or mp.get_push_changes()
216226
mp.log.debug("push change:\n" + pprint.pformat(changes))
@@ -288,16 +298,16 @@ def push_project_async(mc, directory, change_batch=None) -> Optional[UploadJob]:
288298

289299
total_size += file_size
290300

291-
job.total_size = total_size
292-
job.upload_queue_items = upload_queue_items
301+
job.total_size = total_size
302+
job.upload_queue_items = upload_queue_items
293303

294-
mp.log.info(f"will upload {len(upload_queue_items)} items with total size {total_size}")
304+
mp.log.info(f"will upload {len(upload_queue_items)} items with total size {total_size}")
295305

296-
# start uploads in background
297-
job.executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
298-
for item in upload_queue_items:
299-
future = job.executor.submit(_do_upload, item, job)
300-
job.futures.append(future)
306+
# start uploads in background
307+
job.executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
308+
for item in upload_queue_items:
309+
future = job.executor.submit(_do_upload, item, job)
310+
job.futures.append(future)
301311

302312
return job
303313

@@ -435,7 +445,9 @@ def total_upload_size(directory) -> int:
435445
mp = MerginProject(directory)
436446
changes = mp.get_push_changes()
437447
files = changes.get("added", []) + changes.get("updated", [])
438-
return sum(
448+
size = sum(
439449
f.get("diff", {}).get("size", f.get("size", 0))
440450
for f in files
441-
)
451+
)
452+
mp.log.info(f"Upload size of all files is {size}")
453+
return size

mergin/merginproject.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -464,10 +464,6 @@ def get_push_changes(self) -> Dict[str, List[dict]]:
464464
pass
465465

466466
changes["updated"] = [f for f in changes["updated"] if f not in not_updated]
467-
if changes:
468-
self.log.debug(f"All local changes:\n" + pprint.pformat(changes))
469-
else:
470-
self.log.debug("No local changes. Nothing to upload.")
471467
return changes
472468

473469
def copy_versioned_file_for_upload(self, f, tmp_dir):

0 commit comments

Comments
 (0)