-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathupdater.py
More file actions
300 lines (246 loc) · 13.4 KB
/
updater.py
File metadata and controls
300 lines (246 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
#!/usr/bin/env python3
import threading
import time
import os
import requests
import tkinter as tk
from base_manager import BaseManager
from config import resource_path
class UpdateManager(BaseManager):
def __init__(self, config_manager, game_manager, download_manager=None, status_callback=None):
super().__init__(status_callback)
self.config_manager = config_manager
self.game_manager = game_manager
self.download_manager = download_manager
self.is_updating = False # To prevent multiple update downloads at once
def _extract_filename_and_version(self, url, response):
"""Extract filename and version from URL or response headers"""
filename = ""
version = ""
if "content-disposition" in response.headers:
try:
filename = response.headers['content-disposition'].split('filename=')[1].strip('"')
except (IndexError, KeyError):
filename = url.split('/')[-1]
else:
filename = url.split('/')[-1]
if '-v' in filename:
try:
version = filename.split('-v')[1].split('.swf')[0]
except (IndexError, KeyError):
version = str(int(time.time()))
else:
version = str(int(time.time()))
return filename, version
def check_updates(self, root=None):
"""Check for updates to Flash Player and games"""
if self.download_manager and self.download_manager.is_download_in_progress():
self.show_dialog(root, "Download in Progress",
"Cannot check for updates while another download is in progress.",
dialog_type="info")
return
if self.is_updating:
self.show_dialog(root, "Update in Progress",
"An update process is already running.",
dialog_type="info")
return
self.set_status("Checking for updates...")
thread = threading.Thread(target=self._check_updates_thread, args=(root,))
thread.daemon = True
thread.start()
def _check_updates_thread(self, root):
"""Background thread for checking updates"""
try:
updates_available = False
update_messages = []
for game, current_version in self.config_manager.version["games"].items():
if game in self.config_manager.config["game_urls"]:
try:
url = self.config_manager.config["game_urls"][game]
response = requests.head(url, timeout=10)
response.raise_for_status()
_, server_version = self._extract_filename_and_version(url, response)
if not current_version or current_version != server_version:
updates_available = True
update_messages.append(f"{game}: v{current_version or 'none'} → v{server_version}")
except Exception as e:
print(f"Error checking updates for {game}: {str(e)}")
if updates_available and root:
update_text = "Updates available: " + ", ".join(update_messages)
root.after(0, lambda: self.set_status(update_text))
root.after(0, lambda: self._show_update_dialog(root, update_messages))
else:
root.after(0, lambda: self.set_status("No updates available"))
except Exception as e:
root.after(0, lambda: self.set_status(f"Error checking updates: {str(e)}"))
def _show_update_dialog(self, root, update_messages):
"""Show a simple, stateless dialog with available updates"""
update_window = tk.Toplevel(root)
update_window.title("Updates Available")
update_window.geometry("400x320")
update_window.resizable(False, False)
update_window.transient(root)
update_window.grab_set()
self.center_window(update_window, root)
tk.Label(update_window, text="The following updates are available:").pack(pady=10)
updates_frame = tk.Frame(update_window)
updates_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
game_rows = {}
for i, message in enumerate(update_messages):
game = message.split(":")[0]
game_frame = tk.Frame(updates_frame)
game_frame.grid(row=i, column=0, sticky=tk.W+tk.E, pady=2)
game_frame.columnconfigure(0, weight=1)
tk.Label(game_frame, text=message, anchor=tk.W).grid(row=0, column=0, sticky=tk.W)
progress_label = tk.Label(game_frame, text="", width=15, anchor=tk.E)
progress_label.grid(row=0, column=1, sticky=tk.E, padx=5)
download_btn = tk.Button(game_frame, text="Download",
command=lambda g=game: self._download_update(g, game_rows, download_all_btn))
download_btn.grid(row=0, column=2, padx=5, sticky=tk.E)
game_rows[game] = {
'progress_label': progress_label,
'download_btn': download_btn,
'frame': game_frame,
'active': True
}
btn_frame = tk.Frame(update_window)
btn_frame.pack(fill=tk.X, padx=10, pady=10)
download_all_btn = tk.Button(btn_frame, text="Download All",
command=lambda: self._download_all_updates(update_messages, game_rows, download_all_btn))
download_all_btn.pack(side=tk.LEFT, padx=5)
tk.Button(btn_frame, text="Close", command=update_window.destroy).pack(side=tk.RIGHT, padx=5)
def _toggle_buttons(self, game_rows, download_all_btn, state):
"""Enable or disable all download buttons."""
try:
if download_all_btn:
download_all_btn.config(state=state)
for game, row in game_rows.items():
# Only toggle buttons for active rows
if row.get('active', False):
row['download_btn'].config(state=state)
except (tk.TclError, KeyError):
# Window or widgets might have been destroyed
pass
def _download_update(self, game, game_rows, download_all_btn):
"""Download a single game update."""
if self.is_updating:
return
self._toggle_buttons(game_rows, download_all_btn, tk.DISABLED)
self.is_updating = True
thread = threading.Thread(target=self._download_worker, args=([game], game_rows, download_all_btn))
thread.daemon = True
thread.start()
def _download_all_updates(self, update_messages, game_rows, download_all_btn):
"""Download all available updates."""
if self.is_updating:
return
# Get the list of games that are still active in the UI
games_to_download = [game for game, row in game_rows.items() if row.get('active', False)]
if not games_to_download:
self.set_status("All available updates have been downloaded.")
return
self._toggle_buttons(game_rows, download_all_btn, tk.DISABLED)
self.is_updating = True
thread = threading.Thread(target=self._download_worker, args=(games_to_download, game_rows, download_all_btn))
thread.daemon = True
thread.start()
def _download_worker(self, games, game_rows, download_all_btn):
"""Worker thread to download a list of games sequentially."""
for game in games:
try:
ui_row = game_rows.get(game)
if not ui_row:
continue
# Define a thread-safe UI update function
def update_ui(progress, downloaded=None, total=None):
try:
# Update status bar
if progress < 100:
status_msg = f"Downloading {game}: {progress}%"
if downloaded is not None and total is not None and total > 0:
downloaded_mb = downloaded / (1024 * 1024)
total_mb = total / (1024 * 1024)
status_msg = f"Downloading {game}: {progress}% ({downloaded_mb:.1f}/{total_mb:.1f} MB)"
self.set_status(status_msg)
else:
self.set_status(f"Download complete: {game}")
# Update dialog UI
if progress < 100:
progress_text = f"{progress}%"
if downloaded is not None and total is not None and total > 0:
downloaded_mb = downloaded / (1024 * 1024)
total_mb = total / (1024 * 1024)
progress_text = f"{progress}% ({downloaded_mb:.1f}/{total_mb:.1f} MB)"
ui_row['progress_label'].config(text=progress_text)
ui_row['download_btn'].config(text="Downloading...")
else:
ui_row['progress_label'].config(text="Done!")
ui_row['download_btn'].config(text="Downloaded", state=tk.DISABLED)
# Mark as inactive so it's not re-enabled, then schedule for removal
ui_row['active'] = False
if 'frame' in ui_row:
ui_row['frame'].after(500, ui_row['frame'].destroy)
except (tk.TclError, KeyError):
# Widget was destroyed
pass
# Initial UI update
ui_row['progress_label'].after(0, lambda: update_ui(0))
# Download the game
file_path, _ = self._download_game_internal(game, progress_callback=lambda p, d, t: ui_row['progress_label'].after(0, lambda: update_ui(p, d, t)))
if file_path:
# Final UI update for success
ui_row['progress_label'].after(0, lambda: update_ui(100))
else:
# UI update for failure
try:
ui_row['progress_label'].after(0, lambda: ui_row['progress_label'].config(text="Error!"))
ui_row['download_btn'].after(0, lambda: ui_row['download_btn'].config(text="Failed"))
self.set_status(f"Error downloading {game}")
except (tk.TclError, KeyError):
pass
time.sleep(0.5) # Small delay between downloads
except Exception as e:
print(f"Error in download worker for {game}: {str(e)}")
# Re-enable buttons when all downloads are done
self.is_updating = False
self._toggle_buttons(game_rows, download_all_btn, tk.NORMAL)
self.set_status("Update process finished.")
def _download_game_internal(self, game, progress_callback=None, parent=None):
"""Core download functionality."""
try:
url = self.config_manager.config["game_urls"][game]
response = requests.head(url, timeout=10)
response.raise_for_status()
_, version = self._extract_filename_and_version(url, response)
game_filename = f"{game}.swf"
file_path = os.path.join(self.config_manager.games_dir, game_filename)
with requests.get(url, stream=True, timeout=30) as r:
r.raise_for_status()
total_size = int(r.headers.get('content-length', 0))
downloaded = 0
with open(file_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
downloaded += len(chunk)
if progress_callback and total_size > 0:
progress = int((downloaded / total_size) * 100)
progress_callback(progress, downloaded, total_size)
time.sleep(0.01)
self.config_manager.version["games"][game] = version
self.config_manager.save_version_info()
self.set_status(f"{game} v{version} downloaded successfully")
return file_path, version
except Exception as e:
error_msg = f"Failed to download {game}: {str(e)}"
self.set_status(f"Failed to download {game}")
if parent:
self.show_dialog(parent, "Error", error_msg, dialog_type="error")
return None, None
def download_game(self, game, parent=None):
"""Public method to download a game. Delegates to DownloadManager."""
if self.download_manager:
return self.download_manager.download_game(game, parent)
else:
# Fallback if no download manager is provided
return self._download_game_internal(game, parent=parent)[0]