|
3 | 3 | import os |
4 | 4 | import json |
5 | 5 | import shutil |
6 | | -import time |
7 | 6 | import zlib |
8 | 7 | import base64 |
9 | 8 | import urllib.parse |
|
17 | 16 | import re |
18 | 17 | import typing |
19 | 18 | import warnings |
| 19 | +from time import sleep |
20 | 20 |
|
21 | 21 | from typing import List |
22 | 22 |
|
|
41 | 41 | download_diffs_finalize, |
42 | 42 | ) |
43 | 43 | from .client_pull import pull_project_async, pull_project_wait, pull_project_finalize |
44 | | -from .client_push import push_project_async, push_project_wait, push_project_finalize, UploadChunksCache |
| 44 | +from .client_push import get_push_changes_batch, push_project_async, push_project_is_running, push_project_wait, push_project_finalize, UploadChunksCache |
45 | 45 | from .utils import DateTimeEncoder, get_versions_with_file_changes, int_version, is_version_acceptable |
46 | 46 | from .version import __version__ |
47 | 47 |
|
@@ -1486,32 +1486,72 @@ def create_invitation(self, workspace_id: int, email: str, workspace_role: Works |
1486 | 1486 | ws_inv = self.post(f"v2/workspaces/{workspace_id}/invitations", params, json_headers) |
1487 | 1487 | return json.load(ws_inv) |
1488 | 1488 |
|
1489 | | - def sync_project(self, project_dir): |
| 1489 | + def sync_project(self, project_directory): |
1490 | 1490 | """ |
1491 | 1491 | Syncs project by loop with these steps: |
1492 | 1492 | 1. Pull server version |
1493 | 1493 | 2. Get local changes |
1494 | 1494 | 3. Push first change batch |
1495 | 1495 | Repeat if there are more local changes. |
1496 | | - The batch pushing makes use of the server ability to handle simultaneously exclusive upload (that blocks |
1497 | | - other uploads) and non-exclusive upload (for adding assets) |
1498 | 1496 | """ |
1499 | | - attempts = 2 |
1500 | | - for attempt in range(attempts): |
| 1497 | + mp = MerginProject(project_directory) |
| 1498 | + has_changes = True |
| 1499 | + server_conflict_attempts = 0 |
| 1500 | + while has_changes: |
| 1501 | + pull_job = pull_project_async(self, project_directory) |
| 1502 | + if pull_job: |
| 1503 | + pull_project_wait(pull_job) |
| 1504 | + pull_project_finalize(pull_job) |
| 1505 | + |
1501 | 1506 | try: |
1502 | | - pull_job = pull_project_async(self, project_dir) |
1503 | | - if pull_job: |
1504 | | - pull_project_wait(pull_job) |
1505 | | - pull_project_finalize(pull_job) |
1506 | | - |
1507 | | - job = push_project_async(self, project_dir) |
1508 | | - if job: |
1509 | | - push_project_wait(job) |
1510 | | - push_project_finalize(job) |
1511 | | - break |
| 1507 | + job = push_project_async(self, project_directory) |
| 1508 | + if not job: |
| 1509 | + break |
| 1510 | + push_project_wait(job) |
| 1511 | + push_project_finalize(job) |
| 1512 | + _, has_changes = get_push_changes_batch(self, mp, job.server_resp) |
| 1513 | + except ClientError as e: |
| 1514 | + if e.http_error == 409 and server_conflict_attempts < 2: |
| 1515 | + # retry on conflict, e.g. when server has changes that we do not have yet |
| 1516 | + mp.log.info("Attempting sync process due to conflicts between server and local directory or another user is syncing.") |
| 1517 | + server_conflict_attempts += 1 |
| 1518 | + sleep(5) |
| 1519 | + continue |
| 1520 | + raise e |
| 1521 | + |
| 1522 | + def sync_project_with_callback(self, project_directory, progress_callback=None, sleep_time=0.1): |
| 1523 | + """ |
| 1524 | + Syncs project while sending push progress info as callback. |
| 1525 | + Sync is done in this loop: |
| 1526 | + Pending changes? -> Pull -> Get changes batch -> Push the changes -> repeat |
| 1527 | + :param progress_callback: updates the progress bar in CLI, on_progress(increment) |
| 1528 | + :param sleep_time: sleep time between calling the callback function |
| 1529 | + """ |
| 1530 | + mp = MerginProject(project_directory) |
| 1531 | + has_changes = True |
| 1532 | + server_conflict_attempts = 0 |
| 1533 | + while has_changes: |
| 1534 | + pull_job = pull_project_async(self, project_directory) |
| 1535 | + if pull_job: |
| 1536 | + pull_project_wait(pull_job) |
| 1537 | + pull_project_finalize(pull_job) |
| 1538 | + try: |
| 1539 | + job = push_project_async(self, project_directory) |
| 1540 | + if not job: |
| 1541 | + break |
| 1542 | + last = 0 |
| 1543 | + while push_project_is_running(job): |
| 1544 | + sleep(sleep_time) |
| 1545 | + now = job.transferred_size |
| 1546 | + progress_callback(now - last, job) # update progressbar with transferred size increment |
| 1547 | + last = now |
| 1548 | + push_project_finalize(job) |
| 1549 | + _, has_changes = get_push_changes_batch(self, mp, job.server_resp) |
1512 | 1550 | except ClientError as e: |
1513 | | - if e.http_error == 409 and attempt < attempts - 1: |
| 1551 | + if e.http_error == 409 and server_conflict_attempts < 2: |
1514 | 1552 | # retry on conflict, e.g. when server has changes that we do not have yet |
1515 | | - time.sleep(5) |
| 1553 | + mp.log.info("Attempting sync process due to conflicts between server and local directory or another user is syncing.") |
| 1554 | + server_conflict_attempts += 1 |
| 1555 | + sleep(5) |
1516 | 1556 | continue |
1517 | 1557 | raise e |
0 commit comments