Made sprite updating a background task

Also make it use the correct folders for bundled builds
This commit is contained in:
Kevin Cathcart 2017-12-10 11:10:04 -05:00
parent 0372896bc6
commit f44d78d82f
2 changed files with 142 additions and 23 deletions

78
Gui.py
View File

@ -8,7 +8,7 @@ from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, T
from urllib.parse import urlparse from urllib.parse import urlparse
from urllib.request import urlopen from urllib.request import urlopen
from GuiUtils import ToolTips from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
from Main import main, __version__ as ESVersion from Main import main, __version__ as ESVersion
from Rom import Sprite from Rom import Sprite
from Utils import is_bundled, local_path, output_path, open_file from Utils import is_bundled, local_path, output_path, open_file
@ -292,12 +292,6 @@ def guiMain(args=None):
mainWindow.mainloop() mainWindow.mainloop()
def set_icon(window):
er16 = PhotoImage(file=local_path('data/ER16.gif'))
er32 = PhotoImage(file=local_path('data/ER32.gif'))
er48 = PhotoImage(file=local_path('data/ER32.gif'))
window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48)
class SpriteSelector(object): class SpriteSelector(object):
def __init__(self, parent, callback): def __init__(self, parent, callback):
if is_bundled(): if is_bundled():
@ -326,6 +320,7 @@ class SpriteSelector(object):
button.pack(side=LEFT) button.pack(side=LEFT)
set_icon(self.window) set_icon(self.window)
self.window.focus()
def icon_section(self, frame_label, path, no_results_label): def icon_section(self, frame_label, path, no_results_label):
frame = LabelFrame(self.window, text=frame_label, padx=5, pady=5) frame = LabelFrame(self.window, text=frame_label, padx=5, pady=5)
@ -349,23 +344,72 @@ class SpriteSelector(object):
def update_official_sprites(self): def update_official_sprites(self):
# need to wrap in try catch. We don't want errors getting the json or downloading the files to break us. # need to wrap in try catch. We don't want errors getting the json or downloading the files to break us.
self.window.destroy()
self.parent.update()
def work(task):
resultmessage=""
successful = True
def finished():
task.close_window()
if successful:
messagebox.showinfo("Sprite Updater", resultmessage)
else:
messagebox.showerror("Sprite Updater", resultmessage)
SpriteSelector(self.parent, self.callback)
try:
task.update_status("Downloading official sprites list")
sprites_arr = json.loads(temp_sprites_json) sprites_arr = json.loads(temp_sprites_json)
current_sprites = [os.path.basename(file) for file in glob('data/sprites/official/*')] except Exception as e:
resultmessage = "Error getting list of official sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
successful = False
task.queue_event(finished)
return
try:
task.update_status("Determining needed sprites")
current_sprites = [os.path.basename(file) for file in glob(self.official_sprite_dir+'/*')]
official_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr] official_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr]
needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in official_sprites if filename not in current_sprites] needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in official_sprites if filename not in current_sprites]
bundled_sprites=[]
for (sprite_url, filename) in needed_sprites:
target = os.path.join('data/sprites/official',filename)
with urlopen(sprite_url) as response, open(target, 'wb') as out:
shutil.copyfileobj(response, out)
official_filenames = [filename for (_, filename) in official_sprites] official_filenames = [filename for (_, filename) in official_sprites]
obsolete_sprites = [sprite for sprite in current_sprites if sprite not in official_filenames] obsolete_sprites = [sprite for sprite in current_sprites if sprite not in official_filenames]
for sprite in obsolete_sprites: except Exception as e:
os.remove(os.path.join('data/sprites/official', sprite)) resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
successful = False
task.queue_event(finished)
return
self.window.destroy() updated = 0
SpriteSelector(self.parent, self.callback) for (sprite_url, filename) in needed_sprites:
try:
task.update_status("Downloading needed sprite %g/%g" % (updated + 1, len(needed_sprites)))
target = os.path.join(self.official_sprite_dir, filename)
with urlopen(sprite_url) as response, open(target, 'wb') as out:
shutil.copyfileobj(response, out)
except Exception as e:
resultmessage = "Error downloading sprite. Not all sprites updated.\n\n%s: %s" % (type(e).__name__, e)
successful = False
updated += 1
deleted = 0
for sprite in obsolete_sprites:
try:
task.update_status("Removing obsolete sprite %g/%g" % (deleted + 1, len(obsolete_sprites)))
os.remove(os.path.join(self.official_sprite_dir, sprite))
except Exception as e:
resultmessage = "Error removing obsolete sprite. Not all sprites updated.\n\n%s: %s" % (type(e).__name__, e)
successful = False
deleted += 1
if successful:
resultmessage = "official sprites updated sucessfully"
task.queue_event(finished)
BackgroundTaskProgress(self.parent, work, "Updating Sprites")
def browse_for_sprite(self): def browse_for_sprite(self):

View File

@ -1,5 +1,79 @@
import queue
import threading
import tkinter as tk import tkinter as tk
from Utils import local_path
def set_icon(window):
er16 = tk.PhotoImage(file=local_path('data/ER16.gif'))
er32 = tk.PhotoImage(file=local_path('data/ER32.gif'))
er48 = tk.PhotoImage(file=local_path('data/ER32.gif'))
window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48)
# Although tkinter is intended to be thread safe, there are many reports of issues
# some which may be platform specific, or depend on if the TCL library was compiled without
# multithreading support. Therefore I will assume it is not thread safe to avoid any possible problems
class BackgroundTask(object):
def __init__(self, window, code_to_run):
self.window = window
self.queue = queue.Queue()
self.running = True
self.process_queue()
self.task=threading.Thread(target=code_to_run , args=(self,))
self.task.start()
def stop(self):
self.running = False
#safe to call from worker
def queue_event(self, event):
self.queue.put(event)
def process_queue(self):
try:
while True:
if not self.running:
return
event = self.queue.get_nowait()
event()
if self.running:
#if self is no longer running self.window may no longer be valid
self.window.update_idletasks()
except queue.Empty:
pass
if self.running:
self.window.after(100, self.process_queue)
class BackgroundTaskProgress(BackgroundTask):
def __init__(self, parent, code_to_run, title):
self.parent = parent
self.window = tk.Toplevel(parent)
self.window['padx'] = 5
self.window['pady'] = 5
self.window.attributes("-toolwindow",1)
self.window.wm_title(title)
self.labelVar = tk.StringVar()
self.labelVar.set("")
self.label = tk.Label(self.window, textvariable = self.labelVar, width=50)
self.label.pack()
self.window.resizable(width=False, height=False)
set_icon(self.window)
self.window.focus()
super().__init__(self.window, code_to_run)
#safe to call from worker thread
def update_status(self, text):
self.queue_event(lambda text=text: self.labelVar.set(text))
# only call this in an event callback
def close_window(self):
self.stop()
self.window.destroy()
class ToolTips(object): class ToolTips(object):
# This class derived from wckToolTips which is available under the following license: # This class derived from wckToolTips which is available under the following license:
@ -41,6 +115,7 @@ class ToolTips(object):
widget.bind_class(cls.tag, "<Enter>", cls.enter) widget.bind_class(cls.tag, "<Enter>", cls.enter)
widget.bind_class(cls.tag, "<Leave>", cls.leave) widget.bind_class(cls.tag, "<Leave>", cls.leave)
widget.bind_class(cls.tag, "<Motion>", cls.motion) widget.bind_class(cls.tag, "<Motion>", cls.motion)
widget.bind_class(cls.tag, "<Destroy>", cls.leave)
# pick suitable colors for tooltips # pick suitable colors for tooltips
try: try: