44"""
55
66import argparse
7+ import base64
8+ import gzip
79import json
810import os
911import sys
12+ import time
1013import urllib .error
1114import urllib .request
1215from pathlib import Path
1518
1619def read_json_files (directory : str ) -> List [tuple ]:
1720 """
18- Read all JSON files from the specified directory.
21+ Read all JSON files from the specified directory recursively .
1922
2023 Returns:
2124 List of tuples (filename, json_data)
@@ -27,7 +30,7 @@ def read_json_files(directory: str) -> List[tuple]:
2730 print (f"Error: Directory '{ directory } ' does not exist" , file = sys .stderr )
2831 sys .exit (1 )
2932
30- for json_file in release_path .glob ("*.json" ):
33+ for json_file in release_path .rglob ("*.json" ):
3134 try :
3235 with open (json_file , "r" , encoding = "utf-8" ) as f :
3336 data = json .load (f )
@@ -41,61 +44,120 @@ def read_json_files(directory: str) -> List[tuple]:
4144 return json_files
4245
4346
44- def publish_task (
45- url : str , task_data : dict , visibility : str , origin : str , auth_token : str
46- ) -> bool :
47+ def publish_task_batch (
48+ url : str , tasks_batch : List [ tuple ] , visibility : str , origin : str , auth_token : str
49+ ) -> dict :
4750 """
48- Publish a single task to the remote URL.
51+ Publish a batch of tasks to the remote URL with gzip compression and base64 encoding .
4952
5053 Args:
5154 url: The target URL
52- task_data: The JSON data to publish
55+ tasks_batch: List of tuples (filename, task_data)
5356 visibility: Visibility setting ('public' or 'hidden')
5457 origin: Origin of the tasks
5558 auth_token: Authorization token from environment
5659
5760 Returns:
58- True if successful, False otherwise
61+ Dictionary with 'success' count and 'failed' list of filenames
5962 """
60- # Prepare the payload
61- payload = {"task" : task_data , "visibility" : visibility , "origin" : origin }
63+ result = {"success" : 0 , "failed" : []}
64+
65+ # Prepare the tasks list (just the task data, no wrapping)
66+ tasks_list = [task_data for _ , task_data in tasks_batch ]
67+
68+ # Convert to JSON, compress with gzip, and encode with base64
69+ json_data = json .dumps (tasks_list ).encode ("utf-8" )
70+ compressed_data = gzip .compress (json_data )
71+ base64_payload = base64 .b64encode (compressed_data ).decode ("ascii" )
72+
73+ # Prepare the final payload structure matching the API
74+ payload = {
75+ "payload" : base64_payload ,
76+ "visibility" : visibility ,
77+ "origin" : origin ,
78+ }
6279
6380 # Prepare headers
6481 headers = {
6582 "Content-Type" : "application/json" ,
83+ "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ,
6684 }
6785
6886 if auth_token :
6987 headers ["X-AUTH-KEY" ] = auth_token
7088
7189 # Convert payload to JSON bytes
72- data = json .dumps (payload ).encode ("utf-8" )
90+ request_data = json .dumps (payload ).encode ("utf-8" )
7391
7492 # Create request
75- req = urllib .request .Request (url , data = data , headers = headers , method = "POST" )
93+ req = urllib .request .Request (url , data = request_data , headers = headers , method = "POST" )
7694
7795 try :
78- with urllib .request .urlopen (req ) as response :
96+ with urllib .request .urlopen (req , timeout = 30 ) as response :
7997 status = response .status
8098 response_body = response .read ().decode ("utf-8" )
8199 if status in (200 , 201 ):
82- return True
100+ result ["success" ] = len (tasks_batch )
101+ return result
83102 else :
84103 print (f" Warning: Received status { status } " , file = sys .stderr )
85104 print (f" Response: { response_body } " , file = sys .stderr )
86- return False
105+ result ["failed" ] = [filename for filename , _ in tasks_batch ]
106+ return result
87107 except urllib .error .HTTPError as e :
88108 error_body = e .read ().decode ("utf-8" ) if e .fp else ""
89109 print (f" HTTP Error { e .code } : { e .reason } " , file = sys .stderr )
90110 if error_body :
91- print (f" Response: { error_body } " , file = sys .stderr )
92- return False
111+ # Truncate very long error messages
112+ if len (error_body ) > 500 :
113+ print (f" Response: { error_body [:500 ]} ..." , file = sys .stderr )
114+ else :
115+ print (f" Response: { error_body } " , file = sys .stderr )
116+ result ["failed" ] = [filename for filename , _ in tasks_batch ]
117+ return result
93118 except urllib .error .URLError as e :
94119 print (f" URL Error: { e .reason } " , file = sys .stderr )
95- return False
120+ result ["failed" ] = [filename for filename , _ in tasks_batch ]
121+ return result
96122 except Exception as e :
97123 print (f" Error: { e } " , file = sys .stderr )
98- return False
124+ result ["failed" ] = [filename for filename , _ in tasks_batch ]
125+ return result
126+
127+
128+ def load_env_file (env_path : str = ".env" ) -> dict :
129+ """
130+ Load environment variables from a .env file.
131+
132+ Args:
133+ env_path: Path to the .env file
134+
135+ Returns:
136+ Dictionary of environment variables
137+ """
138+ env_vars = {}
139+ env_file = Path (env_path )
140+
141+ if not env_file .exists ():
142+ return env_vars
143+
144+ try :
145+ with open (env_file , "r" , encoding = "utf-8" ) as f :
146+ for line in f :
147+ line = line .strip ()
148+ # Skip empty lines and comments
149+ if not line or line .startswith ("#" ):
150+ continue
151+ # Parse KEY=VALUE format
152+ if "=" in line :
153+ key , value = line .split ("=" , 1 )
154+ # Remove quotes if present
155+ value = value .strip ().strip ("\" '" )
156+ env_vars [key .strip ()] = value
157+ except Exception as e :
158+ print (f"Warning: Error reading .env file: { e } " , file = sys .stderr )
159+
160+ return env_vars
99161
100162
101163def main ():
@@ -121,6 +183,12 @@ def main():
121183 default = "release" ,
122184 help = "Directory containing JSON files (default: release)" ,
123185 )
186+ parser .add_argument (
187+ "--batch-size" ,
188+ type = int ,
189+ default = 20 ,
190+ help = "Number of tasks to send in each batch (default: 20)" ,
191+ )
124192
125193 args = parser .parse_args ()
126194
@@ -131,11 +199,16 @@ def main():
131199
132200 visibility = "public" if args .public else "hidden"
133201
134- # Get auth token from environment
135- auth_token = os .environ .get ("CODEBATTLE_AUTH_TOKEN" , "" )
202+ # Load .env file
203+ env_vars = load_env_file ()
204+
205+ # Get auth token from .env file or environment variable
206+ auth_token = env_vars .get ("CODEBATTLE_AUTH_TOKEN" ) or os .environ .get (
207+ "CODEBATTLE_AUTH_TOKEN" , ""
208+ )
136209 if not auth_token :
137210 print (
138- "Warning: CODEBATTLE_AUTH_TOKEN environment variable not set " ,
211+ "Warning: CODEBATTLE_AUTH_TOKEN not found in .env file or environment " ,
139212 file = sys .stderr ,
140213 )
141214
@@ -150,20 +223,39 @@ def main():
150223 print (f"\n Publishing { len (json_files )} tasks to { args .url } ..." )
151224 print (f"Visibility: { visibility } " )
152225 print (f"Origin: { args .origin } " )
226+ print (f"Batch size: { args .batch_size } " )
153227 print ()
154228
155- # Publish each task
229+ # Publish tasks in batches
156230 success_count = 0
157231 fail_count = 0
232+ total_batches = (len (json_files ) + args .batch_size - 1 ) // args .batch_size
233+
234+ for batch_num in range (0 , len (json_files ), args .batch_size ):
235+ batch = json_files [batch_num : batch_num + args .batch_size ]
236+ batch_index = batch_num // args .batch_size + 1
237+
238+ print (
239+ f"Batch { batch_index } /{ total_batches } ({ len (batch )} tasks)... " ,
240+ end = "" ,
241+ flush = True ,
242+ )
243+
244+ result = publish_task_batch (
245+ args .url , batch , visibility , args .origin , auth_token
246+ )
247+
248+ if result ["success" ] > 0 :
249+ print (f"✓ ({ result ['success' ]} tasks)" )
250+ success_count += result ["success" ]
251+
252+ if result ["failed" ]:
253+ print (f"✗ Failed tasks: { ', ' .join (result ['failed' ])} " )
254+ fail_count += len (result ["failed" ])
158255
159- for filename , task_data in json_files :
160- print (f"Publishing: { filename } ... " , end = "" , flush = True )
161- if publish_task (args .url , task_data , visibility , args .origin , auth_token ):
162- print ("✓" )
163- success_count += 1
164- else :
165- print ("✗" )
166- fail_count += 1
256+ # Add a small delay between batches to avoid rate limiting
257+ if batch_index < total_batches :
258+ time .sleep (1 )
167259
168260 # Summary
169261 print ()
0 commit comments