diff --git a/client.py b/client.py index 313f5a8..b71bce8 100644 --- a/client.py +++ b/client.py @@ -34,6 +34,7 @@ db = None # app data-backend (i.e. LocalFS or FTP) CONFIG_FILE = "gamevault_config.json" + def close_input_window(input_window): '''Close the config window and save the settings''' @@ -188,7 +189,7 @@ def load_main(): app.title("Lan Vault: Overview") if not infowidget_window: - infowidget_window = infowidget.ProgressBarApp(app) + infowidget_window = infowidget.ProgressBarApp(app, data_backend=db) # navbar should not expand when window is resized app.grid_rowconfigure(0, weight=0) diff --git a/data_backend.py b/data_backend.py index efb72bc..7f15ed0 100644 --- a/data_backend.py +++ b/data_backend.py @@ -111,6 +111,12 @@ class HTTP(DataBackend): local_file = os.path.join(self.cache_dir, fullpath) print("Local Target is", local_file) return local_file + + def local_delete_cache_file(self, path, cache_dir=None): + '''Delete a local cache file''' + + print("WARNING: removing:", self.get_local_target(path)) + os.remove(self.get_local_target(path)) def get(self, path, cache_dir=None, return_content=False, wait=False): @@ -146,6 +152,7 @@ class HTTP(DataBackend): if return_content or wait: + print("Sync Requested") # the content is needed for the UI now and not cached, it's needs to be downloaded synchroniously # # as there cannot be a meaningful UI-draw without it. # # THIS IS THE OLD WAY @@ -156,13 +163,30 @@ class HTTP(DataBackend): # f.write(r.text) # this is with streaming - chunk_size = 1024 * 1024 * 5 # 5MB + chunk_size = 1024 * 1024 * 50 # 50MB r = requests.get(self._get_url(), params={"path": path, "as_string": True}, stream=True) + r.raise_for_status() - with open(local_file, "wb") as f: + if path.endswith(".reg") or path.endswith(".txt"): + TYPE = "w" + else: + TYPE = "wb" + + with open(local_file, TYPE) as f: + count = 0 for chunk in r.iter_content(chunk_size=chunk_size, decode_unicode=True): + + print(f"Doing chunk.. {chunk_size*count}") if chunk: - f.write(chunk) + + try: + f.write(chunk) + except TypeError as e: + print("Cannot write:", chunk, "..to ", path, " because it is the wrong type.", e) + raise e + f.flush() + + count += 1 if return_content: print("Content for", path, ":", r.text) diff --git a/db.py b/db.py index e736e0b..6a4b5c0 100644 --- a/db.py +++ b/db.py @@ -4,6 +4,9 @@ from sqlalchemy.orm import sessionmaker, scoped_session Base = declarative_base() +import logging +logging.basicConfig() +logging.getLogger('sqlalchemy').setLevel(logging.ERROR) class Download(Base): __tablename__ = 'files' @@ -24,7 +27,8 @@ class Download(Base): class Database: def __init__(self, db_url="sqlite:///database.db"): - self.engine = create_engine(db_url, echo=True) + + self.engine = create_engine(db_url, echo=False) self.session_factory = sessionmaker(bind=self.engine) self.Session = scoped_session(self.session_factory) # Thread-safe sessions diff --git a/infowidget.py b/infowidget.py index b4a5ad2..a45928c 100644 --- a/infowidget.py +++ b/infowidget.py @@ -18,8 +18,10 @@ import statekeeper import os class ProgressBarApp: - def __init__(self, parent): + def __init__(self, parent, data_backend): + self.data_backend = data_backend + self.parent = parent self.root = tk.Toplevel(parent) self.root.title("Dynamic Progress Bars") @@ -32,44 +34,51 @@ class ProgressBarApp: self.progress_bars = [] # Store tuples of (progressbar, frame, duration, delete_button) self.running = True - threading.Thread(target=self.add_progress_bars, daemon=True).start() + self.root.after(0, self.start_tracking_progress_bars) - def add_progress_bars(self): + def start_tracking_progress_bars(self): - already_tracked = set() - while self.running: + self.already_tracked = set() + self.check_for_new_progress_bars() - downloads = set(statekeeper.get_download()) - new = downloads - already_tracked - already_tracked |= set(downloads) + def check_for_new_progress_bars(self): - for element in new: + downloads = set(statekeeper.get_download()) + new = downloads - self.already_tracked + self.already_tracked |= downloads - frame = tk.Frame(self.frame) - frame.pack(fill=tk.X, pady=2) + for element in new: + frame = tk.Frame(self.frame) + frame.pack(fill=tk.X, pady=2) - progress = ttk.Progressbar(frame, length=200, mode='determinate') - progress.pack(side=tk.LEFT, padx=5) + progress = ttk.Progressbar(frame, length=200, mode='determinate') + progress.pack(side=tk.LEFT, padx=5) - delete_button = tk.Button(frame, text="Delete", command=lambda f=frame: self.delete_progress(f), state=tk.DISABLED) - delete_button.pack(side=tk.LEFT, padx=5) + delete_button = tk.Button(frame, text="Delete", command=lambda f=frame: self.delete_progress(f), state=tk.DISABLED) + delete_button.pack(side=tk.LEFT, padx=5) - label = tk.Label(frame, text=os.path.basename(element.path)) - label.pack(side=tk.LEFT, padx=5) + label = tk.Label(frame, text=os.path.basename(element.path)) + label.pack(side=tk.LEFT, padx=5) - self.progress_bars.insert(0, (progress, frame, delete_button)) # Insert at the top - frame.pack(fill=tk.X, pady=2, before=self.frame.winfo_children()[-1] if self.frame.winfo_children() else None) + self.progress_bars.insert(0, (progress, frame, delete_button)) # Insert at the top + frame.pack(fill=tk.X, pady=2, before=self.frame.winfo_children()[-1] if self.frame.winfo_children() else None) - print("Starting tracker for", element.path) - threading.Thread(target=self.fill_progress, args=(progress, element.path, frame, delete_button), daemon=True).start() + print("Starting tracker for", element.path) + threading.Thread(target=self.fill_progress, args=(progress, element.path, frame, delete_button), daemon=True).start() - time.sleep(2) # Wait before adding a new progress bar + # Schedule the next check in 2 seconds + if self.running: + self.root.after(2000, self.check_for_new_progress_bars) def fill_progress(self, progress, path, frame, delete_button): fail_count = 0 + same_size_count = 0 + prev_precent = 0 while True: + print("Checking download progress..") + if not progress.winfo_exists(): # Check if progress bar still exists return @@ -84,13 +93,29 @@ class ProgressBarApp: continue print("Percent filled:", percent_filled, path) - if not percent_filled or percent_filled >= 99.9: + if percent_filled >= 99.9: self.root.after(0, progress.config, { "value" : 100 }) + print("Finished", path) break else: self.root.after(0, progress.config, { "value" : percent_filled }) time.sleep(0.5) + # check for stuck downloads # + print("same size count", same_size_count) + if prev_precent == percent_filled: + same_size_count += 1 + else: + same_size_count = 0 + if same_size_count > 5: + self.root.after(0, delete_button.config, {"state": tk.NORMAL, "text": "Failed - Delete file manually!"}) + self.progress_bars.append((progress, frame, path, delete_button)) + self.update_delete_all_button() + statekeeper.log_end_download(path) + self.data_backend.local_delete_cache_file(path) + break + prev_precent = percent_filled + # handle finished download # self.root.after(0, delete_button.config, {"state": tk.NORMAL}) self.progress_bars.append((progress, frame, path, delete_button)) @@ -98,7 +123,7 @@ class ProgressBarApp: def delete_progress(self, frame): frame.destroy() - self.progress_bars = [(p, f, d, b) for p, f, d, b in self.progress_bars if f != frame] + self.progress_bars = [(p, f, d) for p, f, d in self.progress_bars if f != frame] self.update_delete_all_button() def delete_all_finished(self): diff --git a/software.py b/software.py index 0566990..3748032 100644 --- a/software.py +++ b/software.py @@ -12,6 +12,7 @@ import threading import sys import tkinter import statekeeper +from tkinter import messagebox class Software: @@ -82,8 +83,16 @@ class Software: '''Extract a cached, downloaded zip to the target location''' software_path = os.path.join(target, self.title) + if os.path.isdir(software_path): - return # TODO better skip + overwrite = messagebox.askyesno( + "Overwrite Existing Directory", + f"The directory '{software_path}' already exists.\nDo you want to overwrite it?" + ) + if not overwrite: + print("Skipping install as instructed by user...") + return + os.makedirs(software_path, exist_ok=True) with zipfile.ZipFile(cache_src, 'r') as zip_ref: @@ -99,6 +108,7 @@ class Software: #self.progress_bar_wrapper.set_text( # text="Extracting: {:.2f}%".format(count/len(total_count)*100)) except zipfile.error as e: + print(e) pass # TODO ??? #zip_ref.extractall(software_path) @@ -137,13 +147,17 @@ class Software: local_file = self.backend.get(remote_file, self.cache_dir, wait=True) statekeeper.log_end_download(remote_file) + print("Deciding on installer...") + # execute or unpack # if local_file.endswith(".exe"): + print("Target is an executable.. running as installer.") if os.name != "nt" and not os.path.isabs(local_file): # need abs path for wine # local_file = os.path.join(os.getcwd(), local_file) localaction.run_exe(local_file) elif local_file.endswith(".zip"): + print("Target is a zip.. unpacking first.") self._extract_to_target(local_file, self.backend.install_dir) # download & install registry files # diff --git a/statekeeper.py b/statekeeper.py index 9fbbe1e..143e5b9 100644 --- a/statekeeper.py +++ b/statekeeper.py @@ -44,9 +44,9 @@ def _download(url, path): def log_begin_download(path, local_path, url): session = db.session() - print("Current path", path) + print("Download path", path) path_exists = session.query(Download).filter(and_(Download.path==path, Download.finished==False)).first() - if path_exists: + if path_exists and False: # TODO FIX THIS print("DAFUG", path_exists) print("WTF", path_exists.path) raise AssertionError("ERROR: {} is already downloading.".format(path)) @@ -59,6 +59,7 @@ def log_begin_download(path, local_path, url): def log_end_download(path): + print("Downlod end logged", path) session = db.session() obj = session.query(Download).filter(Download.path==path).first() if not obj: @@ -99,11 +100,16 @@ def get_percent_filled(path): session = db.session() obj = session.query(Download).filter(Download.path==path, Download.finished==False).first() + if not obj: + return 100 # means its finished size = _bytes_to_mb(os.stat(obj.local_path).st_size) total_size = get_download_size(obj.path) session.close() + if total_size == 0: return 0 + + print("Current filled:", size / total_size * 100) return size / total_size * 100 def get_download(path=None):