33from itertools import groupby
44from os import path , remove
55
6- from click import command , argument , option , style , UsageError
6+ from click import command , argument , option , style , UsageError , Choice
77from cloudinary import api
88
9- from cloudinary_cli .utils .api_utils import query_cld_folder , upload_file , download_file
10- from cloudinary_cli . utils . file_utils import walk_dir , delete_empty_dirs , get_destination_folder , \
11- normalize_file_extension , posix_rel_path
9+ from cloudinary_cli .utils .api_utils import query_cld_folder , upload_file , download_file , get_folder_mode , \
10+ get_default_upload_options , get_destination_folder_options
11+ from cloudinary_cli . utils . file_utils import walk_dir , delete_empty_dirs , normalize_file_extension , posix_rel_path
1212from cloudinary_cli .utils .json_utils import print_json , read_json_from_file , write_json_to_file
13- from cloudinary_cli .utils .utils import logger , run_tasks_concurrently , get_user_action , invert_dict , chunker
13+ from cloudinary_cli .utils .utils import logger , run_tasks_concurrently , get_user_action , invert_dict , chunker , \
14+ group_params , parse_option_value
1415
1516_DEFAULT_DELETION_BATCH_SIZE = 30
1617_DEFAULT_CONCURRENT_WORKERS = 30
3233@option ("-K" , "--keep-unique" , is_flag = True , help = "Keep unique files in the destination folder." )
3334@option ("-D" , "--deletion-batch-size" , type = int , default = _DEFAULT_DELETION_BATCH_SIZE ,
3435 help = "Specify the batch size for deleting remote assets." )
36+ @option ("-fm" , "--folder-mode" , type = Choice (['fixed' , 'dynamic' ], case_sensitive = False ),
37+ help = "Specify folder mode explicitly. By default uses cloud mode configured in your cloud." , hidden = True )
38+ @option ("-o" , "--optional_parameter" , multiple = True , nargs = 2 , help = "Pass optional parameters as raw strings." )
39+ @option ("-O" , "--optional_parameter_parsed" , multiple = True , nargs = 2 ,
40+ help = "Pass optional parameters as interpreted strings." )
3541def sync (local_folder , cloudinary_folder , push , pull , include_hidden , concurrent_workers , force , keep_unique ,
36- deletion_batch_size ):
42+ deletion_batch_size , folder_mode , optional_parameter , optional_parameter_parsed ):
3743 if push == pull :
3844 raise UsageError ("Please use either the '--push' OR '--pull' options" )
3945
4046 sync_dir = SyncDir (local_folder , cloudinary_folder , include_hidden , concurrent_workers , force , keep_unique ,
41- deletion_batch_size )
47+ deletion_batch_size , folder_mode , optional_parameter , optional_parameter_parsed )
4248
4349 result = True
4450 if push :
@@ -53,7 +59,7 @@ def sync(local_folder, cloudinary_folder, push, pull, include_hidden, concurrent
5359
5460class SyncDir :
5561 def __init__ (self , local_dir , remote_dir , include_hidden , concurrent_workers , force , keep_deleted ,
56- deletion_batch_size ):
62+ deletion_batch_size , folder_mode , optional_parameter , optional_parameter_parsed ):
5763 self .local_dir = local_dir
5864 self .remote_dir = remote_dir .strip ('/' )
5965 self .user_friendly_remote_dir = self .remote_dir if self .remote_dir else '/'
@@ -63,15 +69,21 @@ def __init__(self, local_dir, remote_dir, include_hidden, concurrent_workers, fo
6369 self .keep_unique = keep_deleted
6470 self .deletion_batch_size = deletion_batch_size
6571
72+ self .folder_mode = folder_mode or get_folder_mode ()
73+
74+ self .optional_parameter = optional_parameter
75+ self .optional_parameter_parsed = optional_parameter_parsed
76+
6677 self .sync_meta_file = path .join (self .local_dir , _SYNC_META_FILE )
6778
6879 self .verbose = logger .getEffectiveLevel () < logging .INFO
6980
7081 self .local_files = walk_dir (path .abspath (self .local_dir ), include_hidden )
7182 logger .info (f"Found { len (self .local_files )} items in local folder '{ local_dir } '" )
7283
73- self .remote_files = query_cld_folder (self .remote_dir )
74- logger .info (f"Found { len (self .remote_files )} items in Cloudinary folder '{ self .user_friendly_remote_dir } '" )
84+ self .remote_files = query_cld_folder (self .remote_dir , self .folder_mode )
85+ logger .info (f"Found { len (self .remote_files )} items in Cloudinary folder '{ self .user_friendly_remote_dir } ' "
86+ f"({ self .folder_mode } folder mode)" )
7587
7688 local_file_names = self .local_files .keys ()
7789 remote_file_names = self .remote_files .keys ()
@@ -123,19 +135,20 @@ def push(self):
123135 logger .info (f"Uploading { len (files_to_push )} items to Cloudinary folder '{ self .user_friendly_remote_dir } '" )
124136
125137 options = {
126- 'use_filename' : True ,
127- 'unique_filename' : False ,
128- 'invalidate' : True ,
129- 'resource_type' : 'auto'
138+ ** get_default_upload_options ( self . folder_mode ) ,
139+ ** group_params (
140+ self . optional_parameter ,
141+ (( k , parse_option_value ( v )) for k , v in self . optional_parameter_parsed ))
130142 }
143+
131144 upload_results = {}
132145 upload_errors = {}
133146 uploads = []
134147 for file in files_to_push :
135- folder = get_destination_folder ( self .remote_dir , file )
148+ folder_options = get_destination_folder_options ( file , self .remote_dir , self . folder_mode )
136149
137150 uploads .append (
138- (self .local_files [file ]['path' ], {** options , 'folder' : folder }, upload_results , upload_errors ))
151+ (self .local_files [file ]['path' ], {** options , ** folder_options }, upload_results , upload_errors ))
139152
140153 try :
141154 run_tasks_concurrently (upload_file , uploads , self .concurrent_workers )
@@ -186,7 +199,8 @@ def _print_sync_status(self, success, errors):
186199
187200 def _save_sync_meta_file (self , upload_results ):
188201 diverse_filenames = {}
189- for local_path , remote_path in upload_results .items ():
202+ for local_path , remote_res in upload_results .items ():
203+ remote_path = remote_res ["display_path" ] if self .folder_mode == "dynamic" else remote_res ["path" ]
190204 local = normalize_file_extension (posix_rel_path (local_path , self .local_dir ))
191205 remote = normalize_file_extension (posix_rel_path (remote_path , self .remote_dir ))
192206 if local != remote :
@@ -254,7 +268,10 @@ def _get_out_of_sync_file_names(self, common_file_names):
254268 out_of_sync_file_names .add (f )
255269 continue
256270 logger .debug (f"'{ f } ' is in sync" +
257- (f" with '{ self .diverse_file_names [f ]} '" if f in self .diverse_file_names else "" ))
271+ (f" with '{ self .diverse_file_names [f ]} '" if f in self .diverse_file_names else "" ) +
272+ (f". Public ID: { self .recovered_remote_files [f ]['public_id' ]} "
273+ if self .folder_mode == "dynamic" else "" )
274+ )
258275
259276 return out_of_sync_file_names
260277
0 commit comments