11# ../core/update.py
22
3- """Provides functions to update Source.Python's data files."""
3+ """Provides functions to update Source.Python and its data files."""
44
55# =============================================================================
66# >> IMPORTS
3636 'CHECKSUM_URL' ,
3737 'DATA_URL' ,
3838 'DATA_ZIP_FILE' ,
39- 'apply_update_stage1'
40- 'clean_update_dir'
41- 'download_file'
42- 'download_latest_data'
43- 'download_latest_version'
44- 'get_artifacts'
45- 'get_download_url'
46- 'get_latest_data_checksum'
47- 'is_new_data_available'
48- 'unpack_data'
49- 'update_data'
39+ 'download_file' ,
40+ 'get_build_artifacts' ,
41+ 'get_download_url' ,
42+ 'get_latest_data_checksum' ,
43+ 'is_new_data_available' ,
44+ 'update_in_progress' ,
45+ 'update_data' ,
5046)
5147
5248
7268DATA_URL = 'http://data.sourcepython.com/source-python-data.zip'
7369ARTIFACTS_URL = 'http://builds.sourcepython.com/job/Source.Python/lastSuccessfulBuild/api/json?tree=artifacts[relativePath]'
7470BASE_DOWNLOAD_URL = 'http://builds.sourcepython.com/job/Source.Python/lastSuccessfulBuild/artifact/'
71+ DEFAULT_TIMEOUT = 3
72+
73+ #: Indicates, whether an update is in progress (stage 1 has been applied).
74+ update_in_progress = False
7575
7676
7777# =============================================================================
7878# >> FUNCTIONS
7979# =============================================================================
80- def get_latest_data_checksum ( timeout = 3 ):
81- """Return the MD5 checksum of the latest data from the build server .
80+ def download_file ( url_path , file_path , timeout = 3 ):
81+ """Download a file from an URL to a specific file .
8282
83+ :param str url_path:
84+ The URL that should be opened.
85+ :param str file_path:
86+ The file where the content of the URL should be stored.
8387 :param float timeout:
8488 Number of seconds that need to pass until a timeout occurs.
85- :rtype: str
8689 """
87- with urlopen ( CHECKSUM_URL , timeout = timeout ) as url :
88- return url . read (). decode ()
90+ update_logger . log_debug ( f'Downloading file ( { url_path } ) to { file_path } ...' )
91+ now = time . time ()
8992
90- def download_latest_data ( timeout = 3 ) :
91- """Download the latest data from the build server.
93+ with urlopen ( url_path , timeout = timeout ) as url :
94+ data = url . read ()
9295
93- :param float timeout:
94- Number of seconds that need to pass until a timeout occurs.
95- """
96- download_file (DATA_URL , DATA_ZIP_FILE , timeout )
96+ with file_path .open ('wb' ) as f :
97+ f .write (data )
9798
98- def unpack_data ():
99- """Unpack ``source-python-data.zip``."""
100- update_logger .log_debug ('Extracting data in {} ...' .format (DATA_PATH ))
101- with ZipFile (DATA_ZIP_FILE ) as zip :
102- zip .extractall (DATA_PATH )
99+ update_logger .log_info (
100+ 'File has been downloaded. Time elapsed: {:0.2f} seconds' .format (
101+ time .time ()- now ))
103102
104- def update_data (timeout = 3 ):
105- """Download and unpack the latest data from the build server.
106103
107- Old data gets deleted before unpacking.
104+ # =============================================================================
105+ # >> FULL SP UPDATE
106+ # =============================================================================
107+ def do_full_update (timeout = DEFAULT_TIMEOUT ):
108+ """Starts a full update of Source.Python.
109+
110+ A restart of the server and possibly manual work is required. Please see
111+ the Source.Python log for the instructions.
108112
109113 :param float timeout:
110114 Number of seconds that need to pass until a timeout occurs.
111115 """
112- download_latest_data (timeout )
113- if SP_DATA_PATH .isdir ():
114- update_logger .log_debug ('Removing {} ...' .format (SP_DATA_PATH ))
115- SP_DATA_PATH .rmtree ()
116-
117- unpack_data ()
118-
119- def is_new_data_available (timeout = 3 ):
120- """Return ``True`` if new data is available.
116+ global update_in_progress
117+ if update_in_progress :
118+ update_logger .log_message (
119+ 'An update is already in progress. Please follow the instructions '
120+ 'in the log.' )
121+ return
122+
123+ # Make sure there is a clean update directory
124+ _clean_update_dir ()
125+ try :
126+ _download_latest_version (timeout )
127+ _apply_update_stage1 ()
128+ update_in_progress = True
129+ except :
130+ # Make sure to leave a clean update directory, so the loader doesn't
131+ # get confused.
132+ _clean_update_dir ()
133+ raise
134+
135+ def get_build_artifacts (timeout = DEFAULT_TIMEOUT ):
136+ """Return the artifacts of the latest Source.Python build.
121137
122138 :param float timeout:
123139 Number of seconds that need to pass until a timeout occurs.
124- :rtype: bool
125140 """
126- if not DATA_ZIP_FILE .isfile ():
127- return True
128-
129- return DATA_ZIP_FILE .read_hexhash ('md5' ) != get_latest_data_checksum (timeout )
130-
131- def get_artifacts ():
132- """Return the artifacts of the latest build."""
133141 update_logger .log_debug ('Getting artifacts...' )
134- with urlopen (ARTIFACTS_URL ) as url :
142+ with urlopen (ARTIFACTS_URL , timeout = timeout ) as url :
135143 data = json .loads (url .read ())
136-
137144 for d in data ['artifacts' ]:
138145 yield d ['relativePath' ]
139146
140- def get_download_url (game = SOURCE_ENGINE_BRANCH ):
141- """Get the latest download URL for a specific game."""
142- for relative_path in get_artifacts ():
147+ def get_download_url (game = SOURCE_ENGINE_BRANCH , timeout = DEFAULT_TIMEOUT ):
148+ """Get the latest Source.Python download URL for a specific game.
149+
150+ :param str game:
151+ The game game to look for (e.g. ``css``).
152+ :param float timeout:
153+ Number of seconds that need to pass until a timeout occurs.
154+ :rtype: str
155+ :raise ValueError:
156+ Raised if the game wasn't found.
157+ """
158+ for relative_path in get_build_artifacts (timeout ):
143159 if f'-{ game } -' in relative_path :
144160 return BASE_DOWNLOAD_URL + relative_path
145161
146162 raise ValueError (f'Unable to find a download URL for game "{ game } ".' )
147163
148- def download_file (url_path , file_path , timeout = 3 ):
149- """Download a file from an URL to a specific file."""
150- update_logger .log_debug (f'Downloading file ({ url_path } ) to { file_path } ...' )
151- now = time .time ()
152-
153- with urlopen (url_path , timeout = timeout ) as url :
154- data = url .read ()
155-
156- with file_path .open ('wb' ) as f :
157- f .write (data )
158-
159- update_logger .log_info (
160- 'File has been downloaded. Time elapsed: {:0.2f} seconds' .format (
161- time .time ()- now ))
162-
163- def clean_update_dir ():
164+ def _clean_update_dir ():
164165 """Clear or create the update directory."""
165166 if UPDATE_PATH .exists ():
166167 for f in UPDATE_PATH .listdir ():
@@ -171,11 +172,15 @@ def clean_update_dir():
171172 else :
172173 UPDATE_PATH .mkdir ()
173174
174- def download_latest_version (timeout = 3 ):
175- """Download the latest version."""
175+ def _download_latest_version (timeout = DEFAULT_TIMEOUT ):
176+ """Download the latest Source.Python version.
177+
178+ :param float timeout:
179+ Number of seconds that need to pass until a timeout occurs.
180+ """
176181 download_file (get_download_url (), UPDATE_ZIP_FILE , timeout )
177182
178- def apply_update_stage1 ():
183+ def _apply_update_stage1 ():
179184 """Apply stage 1 of the version update."""
180185 update_logger .log_message ('Applying Source.Python update stage 1...' )
181186
@@ -238,3 +243,58 @@ def _apply_update_stage1_linux():
238243
239244 update_logger .log_message (
240245 'Stage 1 has been applied. Restart your server to apply stage 2.' )
246+
247+
248+ # =============================================================================
249+ # >> SP DATA UPDATE
250+ # =============================================================================
251+ def update_data (timeout = DEFAULT_TIMEOUT ):
252+ """Download and unpack the latest data from the build server.
253+
254+ Old data gets deleted before unpacking.
255+
256+ :param float timeout:
257+ Number of seconds that need to pass until a timeout occurs.
258+ """
259+ _download_latest_data (timeout )
260+ if SP_DATA_PATH .isdir ():
261+ update_logger .log_debug ('Removing {} ...' .format (SP_DATA_PATH ))
262+ SP_DATA_PATH .rmtree ()
263+
264+ _unpack_data ()
265+
266+ def is_new_data_available (timeout = DEFAULT_TIMEOUT ):
267+ """Return ``True`` if new data is available.
268+
269+ :param float timeout:
270+ Number of seconds that need to pass until a timeout occurs.
271+ :rtype: bool
272+ """
273+ if not DATA_ZIP_FILE .isfile ():
274+ return True
275+
276+ return DATA_ZIP_FILE .read_hexhash ('md5' ) != get_latest_data_checksum (timeout )
277+
278+ def get_latest_data_checksum (timeout = DEFAULT_TIMEOUT ):
279+ """Return the MD5 checksum of the latest data from the build server.
280+
281+ :param float timeout:
282+ Number of seconds that need to pass until a timeout occurs.
283+ :rtype: str
284+ """
285+ with urlopen (CHECKSUM_URL , timeout = timeout ) as url :
286+ return url .read ().decode ()
287+
288+ def _download_latest_data (timeout = DEFAULT_TIMEOUT ):
289+ """Download the latest data from the build server.
290+
291+ :param float timeout:
292+ Number of seconds that need to pass until a timeout occurs.
293+ """
294+ download_file (DATA_URL , DATA_ZIP_FILE , timeout )
295+
296+ def _unpack_data ():
297+ """Unpack ``source-python-data.zip``."""
298+ update_logger .log_debug ('Extracting data in {} ...' .format (DATA_PATH ))
299+ with ZipFile (DATA_ZIP_FILE ) as zip :
300+ zip .extractall (DATA_PATH )
0 commit comments