Skip to content

Commit 2083800

Browse files
Improve sync error handling
1 parent bf97439 commit 2083800

File tree

4 files changed

+57
-23
lines changed

4 files changed

+57
-23
lines changed

cloudinary_cli/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env python3
2+
import sys
23

34
import click
45
import click_log
@@ -63,4 +64,4 @@ def main():
6364

6465

6566
if __name__ == "__main__":
66-
main()
67+
sys.exit(main())

cloudinary_cli/modules/sync.py

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,23 @@ def sync(local_folder, cloudinary_folder, push, pull, include_hidden, concurrent
4040
sync_dir = SyncDir(local_folder, cloudinary_folder, include_hidden, concurrent_workers, force, keep_unique,
4141
deletion_batch_size)
4242

43+
result = 0
4344
if push:
44-
sync_dir.push()
45+
result = sync_dir.push()
4546
elif pull:
46-
sync_dir.pull()
47+
result = sync_dir.pull()
4748

4849
logger.info("Done!")
4950

51+
return result
52+
5053

5154
class SyncDir:
5255
def __init__(self, local_dir, remote_dir, include_hidden, concurrent_workers, force, keep_deleted,
5356
deletion_batch_size):
5457
self.local_dir = local_dir
5558
self.remote_dir = remote_dir.strip('/')
59+
self.user_friendly_remote_dir = self.remote_dir if self.remote_dir else '/'
5660
self.include_hidden = include_hidden
5761
self.concurrent_workers = concurrent_workers
5862
self.force = force
@@ -67,7 +71,7 @@ def __init__(self, local_dir, remote_dir, include_hidden, concurrent_workers, fo
6771
logger.info(f"Found {len(self.local_files)} items in local folder '{local_dir}'")
6872

6973
self.remote_files = query_cld_folder(self.remote_dir)
70-
logger.info(f"Found {len(self.remote_files)} items in Cloudinary folder '{self.remote_dir}'")
74+
logger.info(f"Found {len(self.remote_files)} items in Cloudinary folder '{self.user_friendly_remote_dir}'")
7175

7276
local_file_names = self.local_files.keys()
7377
remote_file_names = self.remote_files.keys()
@@ -99,10 +103,10 @@ def __init__(self, local_dir, remote_dir, include_hidden, concurrent_workers, fo
99103
self.out_of_sync_remote_file_names = set(self.diverse_file_names.get(f, f) for f in
100104
self.out_of_sync_local_file_names)
101105

102-
skipping = len(common_file_names) - len(self.out_of_sync_local_file_names)
106+
self.synced_files_count = len(common_file_names) - len(self.out_of_sync_local_file_names)
103107

104-
if skipping:
105-
logger.info(f"Skipping {skipping} items")
108+
if self.synced_files_count:
109+
logger.info(f"Skipping {self.synced_files_count} items")
106110

107111
def _get_out_of_sync_file_names(self, common_file_names):
108112
logger.debug("\nCalculating differences...\n")
@@ -130,7 +134,7 @@ def push(self):
130134
if not files_to_push:
131135
return True
132136

133-
logger.info(f"Uploading {len(files_to_push)} items to Cloudinary folder '{self.remote_dir}'")
137+
logger.info(f"Uploading {len(files_to_push)} items to Cloudinary folder '{self.user_friendly_remote_dir}'")
134138

135139
options = {
136140
'use_filename': True,
@@ -139,17 +143,32 @@ def push(self):
139143
'resource_type': 'auto'
140144
}
141145
upload_results = {}
146+
upload_errors = {}
142147
uploads = []
143148
for file in files_to_push:
144149
folder = get_destination_folder(self.remote_dir, file)
145150

146-
uploads.append((self.local_files[file]['path'], {**options, 'folder': folder}, upload_results))
151+
uploads.append(
152+
(self.local_files[file]['path'], {**options, 'folder': folder}, upload_results, upload_errors))
153+
154+
try:
155+
run_tasks_concurrently(upload_file, uploads, self.concurrent_workers)
156+
finally:
157+
self._print_sync_status(upload_results, upload_errors)
158+
self._save_sync_meta_file(upload_results)
147159

148-
run_tasks_concurrently(upload_file, uploads, self.concurrent_workers)
160+
if upload_errors:
161+
raise Exception("Sync did not finish successfully")
149162

150-
self.save_sync_meta_file(upload_results)
163+
def _print_sync_status(self, success, errors):
164+
logger.info("==Sync Status==")
165+
logger.info("===============")
166+
logger.info(f"In Sync| {self.synced_files_count}")
167+
logger.info(f"Synced | {len(success)}")
168+
logger.info(f"Failed | {len(errors)}")
169+
logger.info("===============")
151170

152-
def save_sync_meta_file(self, upload_results):
171+
def _save_sync_meta_file(self, upload_results):
153172
diverse_filenames = {}
154173
for local_path, remote_path in upload_results.items():
155174
local = normalize_file_extension(path.relpath(local_path, self.local_dir))
@@ -163,6 +182,7 @@ def save_sync_meta_file(self, upload_results):
163182
if diverse_filenames or current_diverse_files != self.diverse_file_names:
164183
current_diverse_files.update(diverse_filenames)
165184
try:
185+
logger.debug(f"Updating '{self.sync_meta_file}' file")
166186
write_json_to_file(current_diverse_files, self.sync_meta_file)
167187
logger.debug(f"Updated '{self.sync_meta_file}' file")
168188
except Exception as e:
@@ -175,7 +195,7 @@ def _handle_unique_remote_files(self):
175195
return handled
176196

177197
logger.info(f"Deleting {len(self.unique_remote_file_names)} resources "
178-
f"from Cloudinary folder '{self.remote_dir}'")
198+
f"from Cloudinary folder '{self.user_friendly_remote_dir}'")
179199
files_to_delete_from_cloudinary = list(map(lambda x: self.remote_files[x], self.unique_remote_file_names))
180200

181201
for i in product({"upload", "private", "authenticated"}, {"image", "video", "raw"}):
@@ -204,6 +224,8 @@ def _handle_unique_remote_files(self):
204224
return True
205225

206226
def pull(self):
227+
download_results = {}
228+
download_errors = {}
207229
if not self._handle_unique_local_files():
208230
return False
209231

@@ -218,9 +240,15 @@ def pull(self):
218240
remote_file = self.remote_files[file]
219241
local_path = path.abspath(path.join(self.local_dir, file))
220242

221-
downloads.append((remote_file, local_path))
243+
downloads.append((remote_file, local_path, download_results, download_errors))
244+
245+
try:
246+
run_tasks_concurrently(download_file, downloads, self.concurrent_workers)
247+
finally:
248+
self._print_sync_status(download_results, download_errors)
222249

223-
run_tasks_concurrently(download_file, downloads, self.concurrent_workers)
250+
if download_errors:
251+
raise Exception("Sync did not finish successfully")
224252

225253
def _handle_unique_local_files(self):
226254
handled = self._handle_files_deletion(len(self.unique_local_file_names), "local")

cloudinary_cli/modules/upload_dir.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
@option("-w", "--concurrent_workers", type=int, default=30, help="Specify number of concurrent network threads.")
2525
def upload_dir(directory, optional_parameter, optional_parameter_parsed, transformation, folder, preset,
2626
concurrent_workers):
27-
items, skipped = {}, []
27+
items, skipped = {}, {}
2828
dir_to_upload = abspath(path_join(getcwd(), directory))
2929
logger.info("Uploading directory '{}'".format(dir_to_upload))
3030
parent = dirname(dir_to_upload)

cloudinary_cli/utils/api_utils.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ def query_cld_folder(folder):
4747
return files
4848

4949

50-
def upload_file(file_path, options, uploaded=None, skipped=None):
50+
def upload_file(file_path, options, uploaded=None, failed=None):
5151
uploaded = uploaded if uploaded is not None else {}
52-
skipped = skipped if skipped is not None else []
52+
failed = failed if failed is not None else {}
5353
verbose = logger.getEffectiveLevel() < logging.INFO
5454

5555
try:
@@ -64,11 +64,12 @@ def upload_file(file_path, options, uploaded=None, skipped=None):
6464
uploaded[file_path] = asset_source(result)
6565
except Exception as e:
6666
log_exception(e, f"Failed uploading {file_path}")
67-
skipped.append(file_path)
68-
raise
67+
failed[file_path] = str(e)
6968

7069

71-
def download_file(remote_file, local_path):
70+
def download_file(remote_file, local_path, downloaded=None, failed=None):
71+
downloaded = downloaded if downloaded is not None else {}
72+
failed = failed if failed is not None else {}
7273
makedirs(path.dirname(local_path), exist_ok=True)
7374

7475
sign_url = True if remote_file['type'] in ("private", "authenticated") else False
@@ -79,14 +80,18 @@ def download_file(remote_file, local_path):
7980
result = requests.get(download_url)
8081

8182
if result.status_code != 200:
83+
err = result.headers.get('x-cld-error')
8284
msg = f"Failed downloading: {download_url}, status code: {result.status_code}, " \
83-
f"details: {result.headers.get('x-cld-error')}"
85+
f"details: {err}"
8486
logger.error(msg)
85-
87+
failed[download_url] = err
8688
return
8789

8890
with open(local_path, "wb") as f:
8991
f.write(result.content)
92+
93+
downloaded[remote_file['relative_path']] = local_path
94+
9095
logger.info(style("Downloaded '{}' to '{}'".format(remote_file['relative_path'], local_path), fg="green"))
9196

9297

0 commit comments

Comments
 (0)