Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import tkinter as tk
- from tkinter.filedialog import askdirectory
- import os
- import sys
- import pathlib
- import json
- import threading
- import shutil
- import random
- import re
- # Copies a random file from a specified directory tree to current dir.
- class Consts:
- SAVE_CFG_ON_EXIT = True
- SHOW_TOOLTIPS = True
- # UI
- LABEL_SIZE = 8
- DEFAULT_PAD = 4
- ##### Config #####
- def generate_index(root_dirs, extensions, ignore, regex_ignore, callback):
- global file_index
- callback("Indexing... ")
- try:
- index = []
- sanitized_root_dirs = [val for val in root_dirs if len(val) > 0]
- sanitized_extensions = [val for val in extensions if len(val) > 0]
- sanitized_ignore = [val for val in ignore if len(val) > 0]
- for root_dir in sanitized_root_dirs:
- index += [os.path.join(dp, f).replace('\\', '/') for dp, dn, filenames in os.walk(root_dir) for f in filenames if os.path.splitext(f)[1] in sanitized_extensions]
- if regex_ignore:
- index = [path for path in index if (not any([re.findall(part, path) for part in sanitized_ignore]))]
- else:
- index = [path for path in index if (not any([part in path for part in sanitized_ignore]))]
- file_index = index
- callback("[Indexed "+str(len(index))+" files] Ready ")
- except:
- callback("Something went wrong ")
- def yoink_file(callback):
- global file_index
- try:
- file = random.choice(file_index).strip()
- callback("Yoinked "+file+" ")
- shutil.copy2(file, ".")
- except:
- callback("Something went wrong ")
- ##### Config #####
- def get_datadir():
- """
- Returns a parent directory path
- where persistent application data can be stored.
- # linux: ~/.local/share
- # macOS: ~/Library/Application Support
- # windows: C:/Users/<USER>/AppData/Roaming
- """
- home = pathlib.Path.home()
- if sys.platform == "win32":
- return home / "AppData/Roaming"
- elif sys.platform.startswith("linux"):
- return home / ".local/share"
- elif sys.platform == "darwin":
- return home / "Library/Application Support"
- def save_config():
- print("Saving current config")
- cfg_dir = get_datadir()
- pathlib.Path(cfg_dir).mkdir(parents=True, exist_ok=True)
- cfg_file = cfg_dir / "fileroulette_cfg.json"
- cfg = {}
- cfg["src_dirs"] = [var.get() for var in vars_src_dir]
- cfg["exts"] = var_exts.get()
- cfg["excludes"] = var_excludes.get()
- cfg["regex_excludes"] = var_regex_excludes.get()
- json_str = json.dumps(cfg, indent=4)
- with open(cfg_file, "w") as f:
- f.write(json_str)
- def load_config():
- print("Checking if config exists and loading")
- cfg_dir = get_datadir()
- cfg_file = cfg_dir / "fileroulette_cfg.json"
- if cfg_file.exists():
- lines = []
- with open(cfg_file, "r") as f:
- lines = f.readlines()
- json_str = "\n".join(lines)
- cfg = json.loads(json_str)
- if "exts" in cfg:
- var_exts.set(cfg["exts"])
- if "excludes" in cfg:
- var_excludes.set(cfg["excludes"])
- if "regex_excludes" in cfg:
- var_regex_excludes.set(cfg["regex_excludes"])
- if "src_dirs" in cfg:
- return cfg["src_dirs"]
- else:
- return [""]
- ##### UI #####
- class CreateToolTip(object):
- def __init__(self, widget, text='widget info'):
- if Consts.SHOW_TOOLTIPS:
- self.waittime = 500 #miliseconds
- self.wraplength = 180 #pixels
- self.widget = widget
- self.text = text
- self.widget.bind("<Enter>", self.enter)
- self.widget.bind("<Leave>", self.leave)
- self.widget.bind("<ButtonPress>", self.leave)
- self.id = None
- self.tw = None
- def enter(self, event=None):
- self.schedule()
- def leave(self, event=None):
- self.unschedule()
- self.hidetip()
- def schedule(self):
- self.unschedule()
- self.id = self.widget.after(self.waittime, self.showtip)
- def unschedule(self):
- id = self.id
- self.id = None
- if id:
- self.widget.after_cancel(id)
- def showtip(self, event=None):
- x = y = 0
- x, y, cx, cy = self.widget.bbox("insert")
- x += self.widget.winfo_rootx() + 25
- y += self.widget.winfo_rooty() + 20
- # creates a toplevel window
- self.tw = tk.Toplevel(self.widget)
- # Leaves only the label and removes the app window
- self.tw.wm_overrideredirect(True)
- self.tw.wm_geometry("+%d+%d" % (x, y))
- label = tk.Label(self.tw, text=self.text, justify='left', background="#ffffff", relief='solid', borderwidth=1, wraplength = self.wraplength)
- label.pack(ipadx=1)
- def hidetip(self):
- tw = self.tw
- self.tw= None
- if tw:
- tw.destroy()
- def LabeledInput(parent, title, textvar, hint=None):
- container = tk.Frame(parent)
- label = tk.Label(container, text=title, width=18, anchor=tk.W, justify=tk.LEFT, font=(None, Consts.LABEL_SIZE))
- entry = tk.Entry(container, textvariable=textvar)
- if hint != None:
- CreateToolTip(entry, hint)
- label.pack(side=tk.TOP, fill=tk.X, expand=True)
- entry.pack(side=tk.BOTTOM, fill=tk.X, expand=True, padx=2)
- return container
- def LabeledInputWithActions(parent, title, textvar, action_text, action, hint=None, entry_state=tk.NORMAL, action2_text=None, action2=None):
- container = tk.Frame(parent)
- entry = tk.Entry(container, textvariable=textvar, state=entry_state)
- button = tk.Button(container, text=action_text, command=action)
- if hint != None:
- CreateToolTip(entry, hint)
- if title != None:
- label = tk.Label(container, text=title, width=18, anchor=tk.W, justify=tk.LEFT, font=(None, Consts.LABEL_SIZE))
- label.pack(side=tk.TOP, fill=tk.X, expand=True)
- entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)
- if action2_text != None:
- button2 = tk.Button(container, text=action2_text, command=action2)
- button2.pack(side=tk.RIGHT, expand=False, padx=2)
- button.pack(side=tk.RIGHT, expand=False, padx=2)
- return container
- def LabeledInputWithCheckbox(parent, title, textvar, check_text, check_var, hint=None):
- container = tk.Frame(parent)
- label = tk.Label(container, text=title, width=18, anchor=tk.W, justify=tk.LEFT, font=(None, Consts.LABEL_SIZE))
- entry = tk.Entry(container, textvariable=textvar)
- check = tk.Checkbutton(container, text=check_text, variable=check_var)
- if hint != None:
- CreateToolTip(entry, hint)
- label.pack(side=tk.TOP, fill=tk.X, expand=True)
- entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)
- check.pack(side=tk.RIGHT, expand=False, padx=2)
- return container
- def on_closing():
- save_config()
- window.destroy()
- def apply_settings():
- global vars_src_dir
- dirs = [var.get() for var in vars_src_dir]
- exts = var_exts.get().split(';')
- ignore = var_excludes.get().replace('\\', '\\\\').split(';')
- threading.Thread(target=lambda: generate_index(dirs, exts, ignore, var_regex_excludes.get(), lambda status: var_statusbar_text.set(status))).start()
- def get_next_file():
- print(file_index)
- threading.Thread(target=lambda: yoink_file(lambda status: var_statusbar_text.set(status))).start()
- def rm_src_dir(widget, var):
- global widgets_src_dirs, vars_src_dir
- vars_src_dir.remove(var)
- widgets_src_dirs.remove(widget)
- widget.destroy()
- def add_src_dir(value=""):
- global widgets_src_dirs, vars_src_dir
- idx = len(vars_src_dir)
- new_var = tk.StringVar()
- new_var.set(value)
- vars_src_dir.append(new_var)
- new_input = LabeledInputWithActions(parent=window, title="Source dirs" if idx == 0 else None, textvar=new_var, action_text="Select", action=lambda: new_var.set(askdirectory()), action2_text=" − ", action2=lambda: rm_src_dir(new_input, new_var), hint="The root source directory from which to pull files.\nFiles will also be searched for recursively in all sub-directories.")
- new_input.pack(side=tk.TOP, fill=tk.X, padx=Consts.DEFAULT_PAD, pady=Consts.DEFAULT_PAD)
- widgets_src_dirs.append(new_input)
- ##### Main #####
- window = tk.Tk()
- window.title("FileRoulette")
- file_index = []
- widgets_src_dirs = []
- vars_src_dir = []
- var_exts = tk.StringVar()
- var_excludes = tk.StringVar()
- var_regex_excludes = tk.StringVar()
- var_statusbar_text = tk.StringVar()
- add_dir = LabeledInputWithActions(parent=window, title=None, textvar=None, action_text=" + ", action=add_src_dir, hint="Add another source dir", entry_state=tk.DISABLED)
- ext = LabeledInput(parent=window, title="File types", textvar=var_exts, hint="List of extensions specifying which file types to search for.\nNeeds to be ';' separated.\nE.g. .mp4;.webm")
- excludes = LabeledInputWithCheckbox(parent=window, title="Excludes", textvar=var_excludes, check_text="Regex", check_var=var_regex_excludes, hint="List of exclusions specifying which files to ignore.\nNeeds to be ';' separated.\nIf any part of the file path/name matches, the file will be ignored.\nOptionally can parse each exclude as a regex instead of directly matching.\nAn easy way to exclude entire directories is by doing /dirname1/;/dirname2/")
- apply_resync = tk.Button(window, text="Apply/Resync", command=apply_settings)
- run = tk.Button(window, text="GET", height=3, width=70, command=get_next_file)
- # statusbar inside its own container so that it can have a fixed size and prevent window resizing when status is too long
- container_statusbar = tk.Frame(window)
- statusbar = tk.Label(container_statusbar, bd=1, relief=tk.SUNKEN, anchor=tk.E, bg="lightgray", textvariable=var_statusbar_text)
- container_statusbar.pack(side=tk.BOTTOM, fill=tk.X)
- statusbar.pack(side=tk.BOTTOM, fill=tk.X, ipadx=2, ipady=2)
- run.pack(side=tk.BOTTOM, fill=tk.X, padx=Consts.DEFAULT_PAD, pady=Consts.DEFAULT_PAD)
- apply_resync.pack(side=tk.BOTTOM, fill=tk.X, padx=Consts.DEFAULT_PAD, pady=Consts.DEFAULT_PAD)
- excludes.pack(side=tk.BOTTOM, fill=tk.X, padx=Consts.DEFAULT_PAD, pady=Consts.DEFAULT_PAD)
- ext.pack(side=tk.BOTTOM, fill=tk.X, padx=Consts.DEFAULT_PAD, pady=Consts.DEFAULT_PAD)
- add_dir.pack(side=tk.BOTTOM, fill=tk.X, padx=Consts.DEFAULT_PAD, pady=Consts.DEFAULT_PAD)
- if Consts.SAVE_CFG_ON_EXIT:
- window.protocol("WM_DELETE_WINDOW", on_closing)
- src_dirs = load_config()
- add_src_dir(value=src_dirs[0])
- apply_settings()
- window.update()
- h = window.winfo_height()
- w = window.winfo_width()
- window.minsize(width=w, height=h)
- # prevent statusbar from resizing windown when text is long after the first update
- container_statusbar.pack_propagate(False)
- # min size should not count additional src dir fields so removing doesn't leave a weird gap if multiple values were initially loaded
- for src_dir in src_dirs[1:]:
- add_src_dir(value=src_dir)
- window.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment