class InfoCheck(InfoMaster): def __init__(self, master, value, label, validate_cmd=None): super(InfoCheck, self).__init__(master, value, label, validate_cmd) #self._label = Label(self._master, text="{0}: ".format(data[0])) if OSCONST.os == 'LNX': self._value = tkCheckbutton(self._master, variable=value, command=self._validate_cmd) else: self._value = Checkbutton(self._master, variable=value, command=self._validate_cmd) def _set_value(self, value): self._value.config(variable=value) @property def validate_cmd(self): return self._validate_cmd @validate_cmd.setter def validate_cmd(self, value): # pass the new validate_cmd to the validated entry if required self._validate_cmd = value self._value.config(command=self._validate_cmd)
def add_category(self, category_name): ''' Adds a given category to this object. If the given category already exists, it won't be added again. Category names are assumed to be unique. Args: category_name (str): name of the category to add. ''' if category_name not in self.category_objects.keys(): categories = self.params.get_set_of_parameters(self.category_type) if category_name not in categories: categories.add(category_name) self.params.update_parameter(self.category_type, ';'.join(categories)) self.nb_entries += 1 row_index = self.nb_entries self.options[category_name] = [IntVar(), IntVar(), row_index] box = Label(self.frame_with_boxes, text=category_name, justify=LEFT, wraplength=MIN_COLUMN_WIDTH_NAME - 10) box.grid(row=row_index, column=0, sticky=W, padx=7, pady=5) btn = [] for count in range(2): check_btn = Checkbutton( self.frame_with_boxes, variable=self.options[category_name][count]) check_btn.config(command=( lambda count=count: self.on_select_box(box, count))) btn.append(check_btn) btn[count].grid(row=row_index, column=count + 1, pady=5) self.category_objects[category_name] = (box, btn[0], btn[1]) nd_categories = self.params.get_set_of_parameters( 'NON_DISCRETIONARY_CATEGORIES') if category_name in nd_categories: btn[0].invoke() wd_categories = self.params.get_set_of_parameters( 'WEAKLY_DISPOSAL_CATEGORIES') if category_name in wd_categories: btn[1].invoke() self.update_scroll_region()
def add_category(self, category_name): ''' Adds a given category to this object. If the given category already exists, it won't be added again. Category names are assumed to be unique. Args: category_name (str): name of the category to add. ''' if category_name not in self.category_objects.keys(): categories = self.params.get_set_of_parameters(self.category_type) if category_name not in categories: categories.add(category_name) self.params.update_parameter(self.category_type, ';'.join(categories)) self.nb_entries += 1 row_index = self.nb_entries self.options[category_name] = [IntVar(), IntVar(), row_index] box = Label(self.frame_with_boxes, text=category_name, justify=LEFT, wraplength=MIN_COLUMN_WIDTH_NAME - 10) box.grid(row=row_index, column=0, sticky=W, padx=7, pady=5) btn = [] for count in range(2): check_btn = Checkbutton(self.frame_with_boxes, variable= self.options[category_name][count]) check_btn.config(command=(lambda count=count: self.on_select_box(box, count))) btn.append(check_btn) btn[count].grid(row=row_index, column=count + 1, pady=5) self.category_objects[category_name] = (box, btn[0], btn[1]) nd_categories = self.params.get_set_of_parameters( 'NON_DISCRETIONARY_CATEGORIES') if category_name in nd_categories: btn[0].invoke() wd_categories = self.params.get_set_of_parameters( 'WEAKLY_DISPOSAL_CATEGORIES') if category_name in wd_categories: btn[1].invoke() self.update_scroll_region()
def open_light_settings( self, top, toplevel = 0 ): """ Opens a dialog for modifying the light settings """ L = self.dir['canvas'].config("lighting") frame = tk.Frame(top) vars = [tk.IntVar() for val in range(len(L.settings))] [var.set(val) for var,val in zip(vars,L.settings.values())] def set(key,val): if key == 'texture': self.dir['canvas'].texture = 1-self.dir['canvas'].texture L.settings[key] = self.dir['canvas'].texture elif key == 'fill': self.dir['canvas'].fill = 1-self.dir['canvas'].fill L.settings[key] = self.dir['canvas'].fill else: L.settings[key] = 1-L.settings[key] for i,(key,val) in enumerate(L.settings.items()): choice = Checkbutton(top,text=str(key),variable=vars[i]) choice.config(command=lambda i=i, key=key:set(key,val)) choice.grid(row=i,column=0,sticky='w') if toplevel: top.mainloop()
class App(Tk): #the main class for the main window def __init__(self): Tk.__init__(self) # window properties self.title(string="Screen Recorder") self.iconbitmap("icon.ico") self.resizable(width=False, height=False) ffmpegAvailable = False for item in os.listdir(): if item == "ffmpeg.exe": ffmpegAvailable = True break if not ffmpegAvailable: self.withdraw() if messagebox.askyesno( "FFmpeg Not Found", "ffmpeg.exe could not be found in screen recorder's directory. Do you want to be redirected to the ffmpeg download website?" ): webbrowser.open_new_tab("https://ffmpeg.zeranoe.com/builds/") exit() self.cmdGen = cmdGen( ) # create a command generator object to store settings # file name label1 = Label(self, text="File Name:") label1.grid(row=0, column=0, sticky="") self.entry1 = Entry(self) self.entry1.grid(row=0, column=1, sticky="ew") # ensure the existance of the "ScreenCaptures" directory try: os.mkdir("ScreenCaptures") except FileExistsError: pass os.chdir("ScreenCaptures") # find a default file name that is currently available. defaultFile = "ScreenCapture.mp4" available = False fileNum = 0 while available == False: hasMatch = False for item in os.listdir(): if item == defaultFile: hasMatch = True break if not hasMatch: available = True else: fileNum += 1 defaultFile = "ScreenCapture" + str(fileNum) + ".mp4" os.chdir("..") self.entry1.insert(END, defaultFile) # radio buttons determine what to record self.what = StringVar() self.what.set("desktop") self.radio2 = Radiobutton(self, text="record the window with the title of: ", variable=self.what, value="title", command=self.enDis1) self.radio1 = Radiobutton(self, text="record the entire desktop", variable=self.what, value="desktop", command=self.enDis) self.radio1.grid(row=1, column=0, sticky="w") self.radio2.grid(row=2, column=0, sticky="w") self.entry2 = Entry(self, state=DISABLED) self.entry2.grid(row=2, column=1, sticky="ew") # initialize webcam self.webcamdevices = Webcam.listCam() self.webcamrecorder = Webcam.capturer("") # "record from webcam" checkbox self.rcchecked = IntVar() self.recordcam = Checkbutton(self, text="Record from webcam", command=self.checkboxChanged, variable=self.rcchecked) self.recordcam.grid(row=3, column=0) # a drop-down allowing you to select the webcam device from the available directshow capture devices self.devicename = StringVar(self) if self.webcamdevices: self.devicename.set(self.webcamdevices[0]) self.deviceselector = OptionMenu(self, self.devicename, *self.webcamdevices) self.deviceselector.config(state=DISABLED) self.deviceselector.grid(row=3, column=1) else: self.devicename.set("NO DEVICES AVAILABLE") self.recordcam.config(state=DISABLED) self.deviceselector = OptionMenu(self, self.devicename, "NO DEVICES AVAILABLE") self.deviceselector.config(state=DISABLED) self.deviceselector.grid(row=3, column=1) self.opButton = Button(self, text="⚙ Additional Options...", command=self.openSettings) self.opButton.grid(row=4, column=1, sticky='e') # the "start recording" button self.startButton = Button(self, text="⏺ Start Recording", command=self.startRecord) self.startButton.grid(row=5, column=0, columnspan=2) # some variables self.recording = False # are we recording? self.proc = None # the popen object for ffmpeg (during screenrecord) self.recorder = recordFile.recorder( ) # the "recorder" object for audio (see recordFile.py) self.mergeProcess = None # the popen object for ffmpeg (while merging video and audio files) # start the ffmpeg monitoring callback self.pollClosed() def openSettings(self): self.settings = settingsWin(self, self.cmdGen, self.recorder) def pollClosed(self): """callback that repeats itself every 100ms. Automatically determines if ffmpeg is still running.""" if self.recording: if self.proc.poll() != None: self.startRecord() messagebox.showerror( "ffmpeg error", "ffmpeg has stopped working. ERROR: \n" + str(self.proc.stderr.read()).replace('\\r\\n', '\n')) if self.recorder.error: self.startRecord() if self.mergeProcess and self.recording == False: if self.mergeProcess.poll() != None: self.startButton.config(text="⏺ Start Recording", state=NORMAL) self.title(string="Screen Recorder") self.after(100, self.pollClosed) def enDis(self): """Called when the "desktop" radio button is pressed""" self.entry2.config(state=DISABLED) # self.what.set("desktop") def enDis1(self): """Called when the "window title" radio button is pressed""" self.entry2.config(state=NORMAL) # self.what.set("title") def checkboxChanged(self): """Called when the "record webcam" checkbox is checked or unchecked.""" #self.rcchecked = not self.rcchecked if self.rcchecked.get(): self.deviceselector.config(state=NORMAL) else: self.deviceselector.config(state=DISABLED) def startRecord(self): """toggles recording. Will start conversion subprocess on recording completion""" if self.recording == False: # change the window self.title(string="Screen Recorder (Recording...)") self.startButton.config(text="⏹️ Stop Recording") self.filename = self.entry1.get() # disable interface self.entry1.config(state=DISABLED) self.radio1.config(state=DISABLED) self.radio2.config(state=DISABLED) self.deviceselector.config(state=DISABLED) self.opButton.config(state=DISABLED) if self.what.get() == "title": self.entry2.config(state=DISABLED) # ensure the existence of the "tmp" directory try: os.mkdir("tmp") except FileExistsError: pass # start screen recording process self.recording = True startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = subprocess.SW_HIDE # self.cmdGen.setFps(60) # self.cmdGen.setEncode('nvenc_h264') # CPU: mpeg4 // NVIDIA: h264_nvenc // AMD: no. self.cmdGen.setSource(self.what.get() == "title", self.entry2.get()) command = self.cmdGen.getCmd("tmp/tmp.mkv") self.proc = subprocess.Popen(args=command, startupinfo=startupinfo, stderr=subprocess.PIPE) # start audio recording self.recorder.record("tmp/tmp.wav") # start webcam recording, if checked self.recordcam.config(state=DISABLED) if self.rcchecked.get() and self.webcamdevices: self.webcamrecorder.setDevice(str(self.devicename.get())) self.webcamrecorder.startCapture("tmp/webcamtmp.mkv") # minimize the window to get it out of the way of the recording self.iconify() elif self.recording == True: self.deiconify() defaultFile = self.filename # re-enable interface self.entry1.config(state=NORMAL) self.radio1.config(state=NORMAL) self.radio2.config(state=NORMAL) self.opButton.config(state=NORMAL) if self.webcamdevices: self.recordcam.config(state=NORMAL) if self.rcchecked.get(): self.deviceselector.config(state=NORMAL) if self.what.get() == "title": self.entry2.config(state=NORMAL) available = False fileNum = 0 # stop all recording processes self.recording = False self.proc.terminate() self.recorder.stop_recording() if self.rcchecked.get() and self.webcamdevices: self.webcamrecorder.stopCapture() try: os.mkdir("ScreenCaptures") except FileExistsError: pass # change the window title and button text to reflect the current process self.title(string="Screen Recorder (converting...)") self.startButton.config( text="converting your previous recording, please wait...", state=DISABLED) # start the video conversion process startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = subprocess.SW_HIDE self.cmdGen.config(audList=self.recorder.devices) command = self.cmdGen.getCvtCmd("ScreenCaptures/" + self.filename) if not self.recorder.error: self.mergeProcess = subprocess.Popen(args=command, startupinfo=startupinfo) # if self.rcchecked.get(): # self.mergeProcess = subprocess.Popen(args= ["ffmpeg","-i",'tmp/tmp.mkv','-i','tmp/tmp.wav','-i','tmp/webcamtmp.mkv','-filter_complex','[2:v] scale=640:-1 [inner]; [0:0][inner] overlay=0:0 [out]',"-shortest",'-map','[out]','-y',"ScreenCaptures/"+self.filename]) # else: # self.mergeProcess = subprocess.Popen(args= ["ffmpeg","-i",'tmp/tmp.mkv','-i','tmp/tmp.wav',"-shortest",'-y',"ScreenCaptures/"+self.filename], startupinfo=startupinfo) # change the screen capture name to something that is not taken os.chdir("ScreenCaptures") while True: matches = 0 for item in os.listdir(): if item == defaultFile: matches += 1 if matches == 0: self.entry1.delete(0, END) self.entry1.insert(END, defaultFile) break else: fileNum += 1 file = self.filename.split(".") defaultFile = file[0].rstrip("1234567890") + str( fileNum) + "." + file[1] os.chdir("../")
class App(Tk): def __init__(self, master=None): super().__init__(master) self.style = ThemedStyle(self) self.style.set_theme('elegance') self.iconbitmap(r'data\app.ico') self.minsize(450, 300) self.title('WoT Battle Counter') self.menu_bar = Menu(self) self.content = Frame(self) self.entry = Entry(self.content) self.player_list = Listbox(self.content) self.count_button = Button(self) self.scrollbar = Scrollbar(self.content) self.buttons_frame = Frame(self) self.sort_button = Checkbutton(self.buttons_frame) self.progressbar = Progressbar(self.buttons_frame) self.sort_variable = IntVar(self) self.PlayerObjects = [] self.replays = [] self.player_names = [] self.offset = 0 self.skirmish_value = 1 self.advance_value = 1 self.clan_war_value = 3 def create_app(self): # Config menu entries and attach them self.menu_bar.add_command(label='Config', command=self.open_config_window) self.menu_bar.add_command(label='Open replay files', command=self.open_skirmish_files) self.menu_bar.add_command(label='Open list', command=self.load_list) self.menu_bar.add_command(label='Save list', command=self.save_list) self.menu_bar.add_command(label='Export to file', command=self.export_to_file) self.menu_bar.add_command(label='About', command=about) self.config(menu=self.menu_bar) # Config main content window self.content.pack(fill='both', expand=1) # Config Text Entry + bind enter key self.entry.config(exportselection=0) self.entry.pack(fill='x') self.entry.bind('<Return>', self.add_player) # Config Listbox + bind delete key self.player_list.config(yscrollcommand=self.scrollbar.set) self.scrollbar.config(command=self.player_list.yview, orient='vertical') self.scrollbar.pack(side='right', fill='y') self.player_list.pack(side='left', fill='both', expand=1) self.player_list.bind('<Delete>', self.remove_player) # Count button at the bottom self.count_button.config( text='Count!', command=Thread(target=self.decode_replays).start) self.count_button.pack(side='right', padx=10, pady=10) self.count_button.state(['disabled']) # Config button frame + button + progressbar self.buttons_frame.pack(side='left', fill='both', pady=5, padx=5, expand=1) self.sort_button.config(text="Sort the list", variable=self.sort_variable) self.sort_button.pack(anchor='nw', pady=3, padx=3) self.progressbar.config(length=360, mode='indeterminate', orient=HORIZONTAL, maximum=10) self.progressbar.pack(anchor='e', pady=3, padx=3) # Config the style Style().configure('TEntry', background='white') Style().configure('TButton', font=('Roboto', 12)) Style().configure('OK.TButton', font=('Roboto', 12, 'bold')) # Loading configuration self.load_config() # Start the app self.mainloop() def add_player(self, event): name = self.entry.get() self.entry.delete(0, 'end') player_obj = Player(name) self.PlayerObjects.append(player_obj) if self.sort_variable.get() == 1: self.PlayerObjects.sort(key=lambda player: player.name.lower()) self.player_list.delete(0, 'end') for player in self.PlayerObjects: self.player_list.insert('end', (self.PlayerObjects.index(player) + self.offset + 1, player.name)) else: self.player_list.delete(0, 'end') for player in self.PlayerObjects: self.player_list.insert('end', (self.PlayerObjects.index(player) + self.offset + 1, player.name)) def remove_player(self, event): select = self.player_list.curselection() name = self.player_list.get(select) self.player_list.delete(select) for player in self.PlayerObjects: if name.split()[1] == player.name: self.PlayerObjects.remove(player) self.player_list.delete(0, 'end') for player in self.PlayerObjects: self.player_list.insert('end', (self.PlayerObjects.index(player) + self.offset + 1, player.name)) def open_skirmish_files(self): directory_path = filedialog.askdirectory() if not path.exists(directory_path): return self.replays = self.list_dir(directory_path) self.count_button.state(['!disabled']) def save_list(self): file_path = filedialog.asksaveasfilename(defaultextension='.json') if not path.exists(file_path): return players = list() for player in self.PlayerObjects: players.append(player.name) if path.isfile(file_path): f = open(file_path, 'w') else: f = open(file_path, 'x') f.seek(0) f.write(dumps(players)) def load_list(self): file_path = filedialog.askopenfilename( filetypes=[('json-file', '*.json'), ('all files', '*.*')]) if path.isfile(file_path): self.player_list.delete(0, 'end') f = open(file_path, 'r') players = loads(f.read()) for name in players: player_obj = Player(name) self.PlayerObjects.append(player_obj) for player in self.PlayerObjects: self.player_list.insert('end', (self.PlayerObjects.index(player) + self.offset + 1, player.name)) def export_to_file(self): file_path = filedialog.asksaveasfilename(defaultextension='.txt') if path.isfile(file_path): f = open(file_path, 'w') elif path.exists(file_path): f = open(file_path, 'x') else: return data = str() for player in self.PlayerObjects: if player.battles >= 100: data += f'{player.battles} {player.name} \n' elif player.battles >= 10: data += f'{player.battles} {player.name} \n' elif player.battles > 0: data += f'{player.battles} {player.name} \n' f.seek(0) f.write(data) def list_dir(self, path): entries = listdir(path) re_replay = compile('\.wotreplay') re_file = compile('\.') replays = [] # recursive function for searching in subdirectories for .wotreplay files and putting them into a list for entry in entries: if not search(re_file, entry): new_path = path + "/" + entry new_replays = self.list_dir(new_path) for replay in new_replays: replays.append(replay) elif search(re_replay, entry): replays.append((path + '/' + entry)) elif not search(re_replay, entry) and search(re_file, entry): continue return replays def decode_replays(self): self.progressbar.start() thread_queue = Queue() replay_list_1 = [ self.replays[x] for x in range(0, round(len(self.replays) / 4)) ] replay_list_2 = [ self.replays[x] for x in range(round(len(self.replays) / 4), round(len(self.replays) / 4) * 2) ] replay_list_3 = [ self.replays[x] for x in range( round(len(self.replays) / 4) * 2, round(len(self.replays) / 4) * 3) ] replay_list_4 = [ self.replays[x] for x in range( round(len(self.replays) / 4) * 3, len(self.replays)) ] thread_1 = Thread(target=self.convert_binary_data, args=(replay_list_1, thread_queue)) thread_2 = Thread(target=self.convert_binary_data, args=(replay_list_2, thread_queue)) thread_3 = Thread(target=self.convert_binary_data, args=(replay_list_3, thread_queue)) thread_4 = Thread(target=self.convert_binary_data, args=(replay_list_4, thread_queue)) threads = (thread_1, thread_2, thread_3, thread_4) for thread in threads: thread.start() sleep(1) if self.listen_for_result(threads): self.player_names = thread_queue.get() for name in thread_queue.get(): self.player_names.append(name) for name in thread_queue.get(): self.player_names.append(name) for name in thread_queue.get(): self.player_names.append(name) # COUNTING TIME! for name in self.player_names: for player in self.PlayerObjects: if name[0] == player.name: player.battles += name[1] # Insert names together with battle count back into the list self.player_list.delete(0, 'end') for player in self.PlayerObjects: if player.battles > 0: self.player_list.insert( 'end', (self.PlayerObjects.index(player) + self.offset + 1, player.name, player.battles)) else: continue self.progressbar.stop() def listen_for_result(self, threads): # Check if all replay results have come in alive_threads = 0 for thread in threads: thread.join(0.1) for thread in threads: if thread.is_alive(): print("thread not ded") alive_threads += 1 if alive_threads > 0: if self.listen_for_result(threads): return True return True def convert_binary_data(self, replays, queue): player_names = list() for replay in range(len(replays)): filename_source = replays[replay] f = open(filename_source, 'rb') f.seek(8) size = f.read(4) data_block_size = unpack('I', size)[0] f.seek(12) my_block = f.read(int(data_block_size)) # Convert binary data into a json and then into an iterable tuple json_replay = loads(my_block) players_dict = [(v, k) for (k, v) in dict.items(json_replay['vehicles'])] # Extract names and append to a list for player_id in players_dict: player_name = player_id[0]['name'] if json_replay['battleType'] == 20: player_names.append((player_name, self.skirmish_value)) elif json_replay['battleType'] == 13: player_names.append((player_name, self.clan_war_value)) else: player_names.append((player_name, 1)) queue.put(player_names) def open_config_window(self): config_window = Toplevel(self) config_window.iconbitmap(r'data\app.ico') config_window.minsize(500, 350) config_frame = Labelframe(config_window) config_frame.config(text="App Configuration", relief='groove', borderwidth=5) config_frame.pack(expand=1, fill='both', padx=5, pady=5) offset_title = Label(config_frame) offset_title.config(text='Numbering offset (Default 0)') offset_title.pack(anchor='nw', padx=5, pady=5) offset_entry = Entry(config_frame) offset_entry.config( width=10, exportselection=0, validate='key', validatecommand=(offset_entry.register(validate_config_entry), '%P')) offset_entry.pack(anchor='nw', padx=5, pady=5) battle_value_frame = Labelframe(config_frame) battle_value_frame.config(text='Battle weighting', relief='groove', borderwidth=5) battle_value_frame.pack(anchor='nw', fill='both', expand=1, padx=5, pady=5) descriptor_frame = Frame(battle_value_frame) descriptor_frame.pack(side='left', fill='both', expand=1) entry_frame = Frame(battle_value_frame) entry_frame.pack(side='left', fill='both', expand=1) skirmish_title = Label(descriptor_frame) skirmish_title.config(text='Skirmish weighting (Default = 1):') skirmish_title.pack(anchor='nw', padx=5, pady=7) skirmish_entry = Entry(entry_frame) skirmish_entry.config( width=10, exportselection=0, validate='key', validatecommand=(skirmish_entry.register(validate_config_entry), '%P')) skirmish_entry.pack(anchor='nw', padx=5, pady=5) advance_title = Label(descriptor_frame) advance_title.config(text='Advance weighting (Default = 1):') advance_title.pack(anchor='nw', padx=5, pady=10) advance_entry = Entry(entry_frame) advance_entry.config( width=10, exportselection=0, validate='key', validatecommand=(advance_entry.register(validate_config_entry), '%P')) advance_entry.pack(anchor='nw', padx=5, pady=5) clan_war_title = Label(descriptor_frame) clan_war_title.config(text='Clan War weighting (Default = 3):') clan_war_title.pack(anchor='nw', padx=5, pady=6) clan_war_entry = Entry(entry_frame) clan_war_entry.config( width=10, exportselection=0, validate='key', validatecommand=(clan_war_entry.register(validate_config_entry), '%P')) clan_war_entry.pack(anchor='nw', padx=5, pady=5) buttons_frame = Frame(config_frame) buttons_frame.pack(anchor='sw', fill='both', expand=0) apply_button = Button(buttons_frame) apply_button.config(text='Apply', command=partial(self.config_apply, offset_entry, skirmish_entry, advance_entry, clan_war_entry)) apply_button.pack(side='right', padx=5, pady=5) cancel_button = Button(buttons_frame) cancel_button.config(text='Cancel', command=lambda: config_window.destroy()) cancel_button.pack(side='right', padx=5, pady=5) ok_button = Button(buttons_frame) ok_button.config(text='OK', style='OK.TButton', command=partial(self.config_ok, offset_entry, skirmish_entry, advance_entry, clan_war_entry, config_window)) ok_button.pack(side='right', padx=5, pady=5) offset_entry.insert('end', self.offset) skirmish_entry.insert('end', self.skirmish_value) advance_entry.insert('end', self.advance_value) clan_war_entry.insert('end', self.clan_war_value) def config_ok(self, offset, skirmish, advance, clan_war, window): self.offset = int(offset.get()) self.skirmish_value = int(skirmish.get()) self.advance_value = int(advance.get()) self.clan_war_value = int(clan_war.get()) data = { 'offset': offset.get(), 'skirmish_value': skirmish.get(), 'advance_value': advance.get(), 'clan_war_value': clan_war.get() } if path.isfile(r'config.json'): f = open(r'config.json', 'w') else: f = open(r'config.json', 'x') f.seek(0) f.write(dumps(data)) window.destroy() def config_apply(self, offset, skirmish, advance, clan_war): self.offset = int(offset.get()) self.skirmish_value = int(skirmish.get()) self.advance_value = int(advance.get()) self.clan_war_value = int(clan_war.get()) data = { 'offset': offset.get(), 'skirmish_value': skirmish.get(), 'advance_value': advance.get(), 'clan_war_value': clan_war.get() } if path.isfile(r'config.json'): f = open(r'config.json', 'w') else: f = open(r'config.json', 'x') f.seek(0) f.write(dumps(data)) def load_config(self): if path.isfile(r'config.json'): f = open(r'config.json', 'r') data = loads(f.read()) print(data) self.offset = int(data['offset']) self.skirmish_value = int(data['skirmish_value']) self.advance_value = int(data['advance_value']) self.clan_war_value = int(data['clan_war_value']) else: pass
class Application(Frame): def __init__(self, master=None): super().__init__(master) self.master = master self.grid(row=0, column=0, sticky="nesw", padx=20, pady=20) self._create_widgets() self._selected_genre = set() self._helper = None self._books = [] self._is_started = False def _add_titles_to_listbox(self): books = [] self.books.delete(0, tk.END) for genre in sorted(self._selected_genre): books.extend(self._helper.get_books_in_genres(self._books, genre)) books.sort() self.books.insert(tk.END, *books) def _create_widgets(self): def _create_label(frame, text, coordinates): label = Label(frame, text=text, justify=tk.LEFT, anchor="w", width=20) label.grid(row=coordinates[0], column=coordinates[1]) _create_label(self, "Output Folder:", (0, 0)) self.output_path = Entry(self) self.output_path.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(0, 15)) _create_label(self, "Available Genres:", (2, 0)) _create_label(self, "Books to Download:", (2, 1)) self.genres = tk.Listbox(self, selectmode="multiple") def on_genre_select(evt): indices = evt.widget.curselection() self._selected_genre.clear() for index in indices: value = evt.widget.get(index) self._selected_genre.add(value) self._add_titles_to_listbox() self.genres.bind("<<ListboxSelect>>", on_genre_select) self.genres["height"] = 20 self.genres["width"] = 30 self.genres.grid(row=3, column=0, padx=(0, 10)) self.books = tk.Listbox(self) self.books["height"] = 20 self.books["width"] = 30 self.books.grid(row=3, column=1, padx=(10, 0)) self._pdf = tk.IntVar(value=1) self.pdf = Checkbutton(self, text="PDF", variable=self._pdf) self.pdf.grid(row=4, column=0, pady=10) self._epub = tk.IntVar(value=1) self.epub = Checkbutton(self, text="EPUB", variable=self._epub) self.epub.grid(row=4, column=1, pady=10) self._status_text = tk.StringVar() self.status = Label(self, textvariable=self._status_text) self.status.grid(row=5, column=0, columnspan=2) self._current_bar_value = tk.IntVar(value=0) self.bar = Progressbar(self, variable=self._current_bar_value) self._download_btn = tk.StringVar() db = Button(self, textvariable=self._download_btn) db["command"] = self.start_download self._download_btn.set("Download") db.grid(row=7, column=0, columnspan=2, pady=10) LOG.info("All widgets created.") def set_output_folder(self, path): if self.output_path: self.output_path.insert(tk.END, path) def populate_genres(self, genres): self._genre_mapping = genres for genre in sorted(genres): self.genres.insert(tk.END, genre) LOG.info("Added {} genres".format(len(genres))) def start_download(self): titles = self.books.get(0, tk.END) if not titles: self._status_text.set("Please select a genre to continue") return self._is_started = not self._is_started self._toggle_state(not self._is_started) if self._is_started: books = self._books.copy() for book in books.iterrows(): if book[1]["Book Title"] not in titles: books = books.drop(index=book[0]) LOG.info("Starting to download {} books".format(len(books))) self.bar["maximum"] = len(books) self._helper.STOP_FLAG = False self._download_thread = threading.Thread( daemon=True, target=self._helper.download_books, args=(books, self.output_path.get()), kwargs={ "pdf": self._pdf.get(), "epub": self._epub.get(), "verbose": True, "label": self._status_text, "progressbar": self._current_bar_value, }, ) self._download_thread.start() self._thread_check_in(self._download_thread) else: self._helper.STOP_FLAG = True def _thread_check_in(self, thread, period=100): if thread.isAlive(): self.master.after(period, self._thread_check_in, thread, period) else: self._is_started = False self._toggle_state(True) def _toggle_state(self, enable): state = tk.NORMAL if enable else tk.DISABLED text = "Download" if enable else "Stop" status = "" if enable else "Starting downloader ..." self.output_path.config(state=state) self.genres.config(state=state) self.books.config(state=state) self.pdf.config(state=state) self.epub.config(state=state) self._download_btn.set(text) self._status_text.set(status) if enable: self.bar.grid_remove() else: self.bar.grid(row=6, column=0, columnspan=2, pady=10, sticky="ew")
class SettingsEditor(Frame): def __init__(self, master, app_main, **kwargs): super().__init__(master, **kwargs) self.app = app_main self.create_settings_ui() def create_settings_ui(self): self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) self.frm_settings = Frame(self) # self.frm_settings.rowconfigure(2, weight=1) # self.frm_settings.columnconfigure(0, weight=1) is_valid_req_delay = self.register(functools.partial(_is_number, min=0)) is_valid_duration = self.register( functools.partial(_is_number, min=0, max=20)) is_valid_history_retention = self.register( functools.partial(_is_number, min=0, max=100)) is_valid_max_conns = self.register( functools.partial(_is_number, min=1, max=20, integer=True)) is_valid_num_workers = self.register( functools.partial(_is_number, min=0, max=os.cpu_count() or 8, integer=True)) self.frm_settings.grid(padx=10, pady=10, sticky='nsew') frm_basic = LabelFrame(self.frm_settings, text='Basic') frm_basic.grid(padx=5, pady=5, sticky='nsew', row=0, column=0, ipadx=5) lbl = Label(frm_basic, text='League:') lbl.grid(row=0, column=0, padx=5, pady=3, sticky='w') self.cmb_league = Combobox(frm_basic, state=READONLY, values=leagueOptions) self.cmb_league.grid(row=0, column=1, pady=3, sticky='nsew') # lbl = Label(frm_basic, text='Minimum request delay(s):') # lbl.grid(row=1, column=0, padx=5, pady=3, sticky='w') # self.entry_req_delay = Entry(frm_basic, validate='all', validatecommand=(is_valid_req_delay, '%P')) # self.entry_req_delay.grid(row=1, column=1, pady=3, sticky='nsew') # lbl = Label(frm_basic, text='Scan mode:') # lbl.grid(row=2, column=0, padx=5, pady=3, sticky='w') # self.cmb_scan_mode = Combobox(frm_basic, state=READONLY, values=scanModeOptions) # self.cmb_scan_mode.grid(row=2, column=1, pady=3, sticky='nsew') lbl = Label(frm_basic, text='Notification duration(s):') lbl.grid(row=3, column=0, padx=5, pady=3, sticky='w') self.entry_notification_duration = Entry( frm_basic, validate='all', validatecommand=(is_valid_duration, '%P')) self.entry_notification_duration.grid(row=3, column=1, pady=3, sticky='nsew') frm = LabelFrame(self.frm_settings, text='Advanced') frm.grid(pady=5, sticky='nsew', row=0, column=1, ipadx=5) lbl = Label(frm, text='Scan mode:') lbl.grid(row=0, column=0, padx=5, pady=3, sticky='w') self.cmb_scan_mode = Combobox(frm, state=READONLY, values=scanModeOptions) self.cmb_scan_mode.grid(row=0, column=1, pady=3, sticky='nsew') lbl = Label(frm, text='Min. request delay:') lbl.grid(row=1, column=0, padx=5, pady=3, sticky='w') self.entry_req_delay = Entry(frm, validate='all', validatecommand=(is_valid_req_delay, '%P')) self.entry_req_delay.grid(row=1, column=1, pady=3, sticky='nsew') lbl = Label(frm, text='(seconds)') lbl.grid(row=1, column=2, padx=(5, 0), pady=3, sticky='w') lbl = Label(frm, text='Max connections:') lbl.grid(row=2, column=0, padx=5, pady=3, sticky='w') self.entry_max_conns = Entry(frm, validate='all', validatecommand=(is_valid_max_conns, '%P')) self.entry_max_conns.grid(row=2, column=1, pady=3, sticky='nsew') lbl = Label(frm, text='Parsers #:') lbl.grid(row=3, column=0, padx=5, pady=3, sticky='w') self.entry_num_workers = Entry(frm, validate='all', validatecommand=(is_valid_num_workers, '%P')) self.entry_num_workers.grid(row=3, column=1, pady=3, sticky='nsew') lbl = Label(frm, text='(0 = Auto)') lbl.grid(row=3, column=2, padx=(5, 0), pady=3, sticky='w') lbl = Label(frm, text='History retention:') lbl.grid(row=4, column=0, padx=5, pady=3, sticky='w') self.entry_history_retention = Entry( frm, validate='all', validatecommand=(is_valid_history_retention, '%P')) self.entry_history_retention.grid(row=4, column=1, pady=3, sticky='nsew') lbl = Label(frm, text='(days)') lbl.grid(row=4, column=2, padx=(5, 0), pady=3, sticky='w') frm = Frame(frm_basic) frm.grid(row=4, column=0) self.var_notify = BooleanVar() self.var_notify.trace_variable( 'w', lambda a, b, c: self._on_notify_option_change()) self.cb_notifications = Checkbutton(frm, text='Growl notifications', variable=self.var_notify) self.cb_notifications.grid(row=0, column=0, padx=5, pady=3, sticky='w') self.var_notify_copy = BooleanVar() self.cb_notify_copy = Checkbutton(frm, text='Copy message', variable=self.var_notify_copy) self.cb_notify_copy.grid(row=1, column=0, padx=5, pady=3, sticky='w') self.var_notify_play_sound = BooleanVar() self.cb_notify_play_sound = Checkbutton( frm, text='Play sound', variable=self.var_notify_play_sound) self.cb_notify_play_sound.grid(row=2, column=0, padx=5, pady=3, sticky='w') frm_btns = Frame(self.frm_settings) frm_btns.grid(row=2, columnspan=3, pady=(20, 5), sticky='w') self.btn_apply = Button(frm_btns, text='Apply', command=self.applyChanges) self.btn_apply.grid(row=0, column=0, padx=5) self.btn_reload = Button(frm_btns, text='Reload', command=self.loadSettings) self.btn_reload.grid(row=0, column=1) def _on_notify_option_change(self): state = NORMAL if self.var_notify.get() else DISABLED self.cb_notify_copy.config(state=state) self.cb_notify_play_sound.config(state=state) def applyChanges(self): cfg = AppConfiguration() cfg.league = self.cmb_league.get() or leagueOptions[0] cfg.notify = self.var_notify.get() cfg.notify_copy_msg = self.var_notify_copy.get() cfg.notify_play_sound = self.var_notify_play_sound.get() cfg.notification_duration = float( self.entry_notification_duration.get() or 4) cfg.request_delay = float(self.entry_req_delay.get() or 0.7) cfg.scan_mode = self.cmb_scan_mode.get() or scanModeOptions[0] cfg.history_retention = int(self.entry_history_retention.get() or 1) cfg.max_conns = int(self.entry_max_conns.get() or 8) cfg.num_workers = int(self.entry_num_workers.get() or 0) cfg.smooth_delay = config.smooth_delay self.app.update_configuration(cfg) def loadSettings(self): self.cmb_league.set(config.league) self.cmb_scan_mode.set(config.scan_mode) self.entry_notification_duration.delete(0, END) self.entry_notification_duration.insert(0, config.notification_duration) self.var_notify.set(config.notify) self.var_notify_copy.set(config.notify_copy_msg) self.var_notify_play_sound.set(config.notify_play_sound) self.entry_req_delay.delete(0, END) self.entry_req_delay.insert(0, config.request_delay) self.entry_history_retention.delete(0, END) self.entry_history_retention.insert(0, config.history_retention) self.entry_max_conns.delete(0, END) self.entry_max_conns.insert(0, config.max_conns) self.entry_num_workers.delete(0, END) self.entry_num_workers.insert(0, config.num_workers)
class TkApp(Tk): """ The main Tk class for the gui of simplebackup """ def __init__(self, **kwargs): super().__init__() title = "Simple Backup | V" + __version__ self.wm_title(title) self.protocol("WM_DELETE_WINDOW", self.on_closing) self.__thread = None self.__files_found = 0 self.__files_copied = 0 config_fn = kwargs.get("config_fn", user_config_filepath()) self.__app_config = Config_Handler(config_fn) self.__curr_config = self.__app_config.default_config_i self.__menu = Menu(self) self.__menu_file = Menu(self.__menu, tearoff=0) self.__menu_file.add_command(label="Quit", command=self.quit) self.__menu_config = Menu(self.__menu, tearoff=0) self.__menu_config.add_command(label="New", command=self.new_config) self.__menu_config.add_command(label="Load", command=self.switch_config) self.__menu_config.add_command(label="Change Default", command=self.change_default_config) self.__menu_config.add_command(label="Rename Current", command=self.rename_curr_conf) self.__menu_config.add_separator() self.__menu_config.add_command(label="Delete Current", command=self.delete_current_config) self.__menu_config.add_command(label="Delete All", command=self.reset_config) self.__menu_help = Menu(self.__menu, tearoff=0) self.__menu_help.add_command(label="Check for Updates", command=self.show_update_popup) self.__menu_help.add_command(label="About", command=self.show_about_popup) self.__menu.add_cascade(label="File", menu=self.__menu_file) self.__menu.add_cascade(label="Config", menu=self.__menu_config) self.__menu.add_cascade(label="Help", menu=self.__menu_help) self.__title_l = Label(self, text=title, font=(16)) self.__curr_config_name_l = Label(self) self.__last_backup_l = Label(self) self.__set_versions_to_keep = Button( self, text="Set Versions To Keep", command=self.update_versions_to_keep) self.__versions_to_keep_l = Label(self) self.__inc_folder_bnt = Button(self, text="Include Another Folder", command=self.add_included_folder) self.__included_folders_lb = Listbox(self, height=4) self.__included_folders_lb.bind("<<ListboxSelect>>", self.remove_selected_included_folder) self.__included_folders_lb.bind('<FocusOut>', self.deselect_included_folder) self.__excl_folder_bnt = Button(self, text="Exclude Another Folder", command=self.add_excluded_folder) self.__excluded_folders_lb = Listbox(self, height=4) self.__excluded_folders_lb.bind("<<ListboxSelect>>", self.remove_selected_excluded_folder) self.__excluded_folders_lb.bind('<FocusOut>', self.deselect_excluded_folder) self.__backup_to_bnt = Button(self, text="Backup Folder", command=self.set_backup_folder) self.__backup_folder_l = Label(self) self.__use_tar_l = Label(self, text="Use Tar") self.__use_tar_var = BooleanVar(self) self.__use_tar_var.trace_add("write", self.use_tar_changed) self.__use_tar = Checkbutton(self, variable=self.__use_tar_var) self.__backup_start_bnt = Button(self, text="Start Backup", command=self.start_backup) self.__progress = Progressbar(self) self.__statusbar = Label(self, text="ok", relief=SUNKEN, anchor=W) self._load_display() self._layout() if self.__app_config.show_help: self.show_help_popup() def on_closing(self): """ called on window close """ if self.__files_found != self.__files_copied: if messagebox.askyesno("Backup Running", "Do you want to stop the backup?"): self.destroy() else: self.destroy() def _load_display(self): """ load the widgets with data from the current backup config, should be run after loading a config from file and at app launch """ self.__versions_to_keep = self.__app_config.get_versions_to_keep( self.__curr_config) self.__included_folders = self.__app_config.get_included_folders( self.__curr_config) self.__excluded_folders = self.__app_config.get_excluded_folders( self.__curr_config) self.__backup_location = self.__app_config.get_backup_path( self.__curr_config) curr_conf_name = self.__app_config.get_config_name(self.__curr_config) self.__curr_config_name_l.config(text=f"Config Name: {curr_conf_name}") self.__last_backup_l.config( text= f"Last Known Backup: {self.__app_config.get_human_last_backup(self.__curr_config)}" ) self.__versions_to_keep_l.config(text=self.__versions_to_keep) self.__included_folders_lb.delete(0, END) self.__included_folders_lb.insert(0, *self.__included_folders) self.__excluded_folders_lb.delete(0, END) self.__excluded_folders_lb.insert(0, *self.__excluded_folders) self.__backup_folder_l.config(text=str(self.__backup_location)) self.__use_tar_var.set( self.__app_config.get_use_tar(self.__curr_config)) def switch_config(self): """ switches what config to use for backup, asks the user for a config to load, then loads the display """ next_combo = ask_combobox("Load Config", "Config Name", self.__app_config.get_config_names()) if next_combo != None: self.__curr_config = next_combo self._load_display() def change_default_config(self): """ switches what config to use for the default backup, asks the user for a config to load """ next_combo = ask_combobox("Default Config", "Config Name", self.__app_config.get_config_names()) if next_combo != None: self.__app_config.default_config_i = next_combo def rename_curr_conf(self): """ rename a existing config, will ask the user in a popup string input """ new_name = simpledialog.askstring("Rename Config", "New Name") if new_name: self.__app_config.rename_config(self.__curr_config, new_name) self._load_display() def new_config(self): """ creates a new empty backup config, asks the user for config name """ name = simpledialog.askstring("New Config", "Config Name") if name: self.__app_config.create_config(name) def delete_current_config(self): """ deletes the current selected config, asks the user to confirm """ if messagebox.askyesno( "Confirm Delete", "Are you sure you want to delete the current config?"): self.__app_config.remove_config(self.__curr_config) self.__curr_config = self.__app_config.default_config_i self._load_display() def reset_config(self): """ resets all the user configs, asks the user to confirm """ if messagebox.askyesno( "Confirm Reset", "Are you sure you want to reset the all configurations?"): self.__app_config.reset_config() self.__curr_config = self.__app_config.default_config_i self._load_display() def use_tar_changed(self, *args): """ called each time the __use_tar_var is called """ self.__app_config.set_use_tar(self.__curr_config, self.__use_tar_var.get()) def update_versions_to_keep(self): """ update the number of versions to keep, asks the user for a integer """ new_val = simpledialog.askinteger( "Versions To Keep", "How many backups do you want to keep", minvalue=0) if new_val != self.__versions_to_keep and new_val != None: self.__versions_to_keep = new_val self.__app_config.set_versions_to_keep(self.__curr_config, self.__versions_to_keep) self.__versions_to_keep_l.config(text=self.__versions_to_keep) def deselect_included_folder(self, *args): """ deselects the selected element in included folder """ self.__included_folders_lb.selection_clear(0, END) def deselect_excluded_folder(self, *args): """ deselects the selected element in excluded folder """ self.__excluded_folders_lb.selection_clear(0, END) def add_included_folder(self): """ add a folder to include in the backup, will ask user for a directory """ folder = filedialog.askdirectory(initialdir="/", title="Select Folder To Backup") if folder: folder_path = Path(folder) if folder_path != self.__backup_location: self.__included_folders.append(folder_path) self.__included_folders_lb.insert(END, folder_path) self.__app_config.set_included_folders(self.__curr_config, self.__included_folders) else: messagebox.showwarning( title="Folder Same As Backup Path", message= "You selected a folder that was the same as the backup path!" ) def remove_selected_included_folder(self, *args): """ remove the currently selected item in the included folders ListBox, will ask the user to confirm """ curr_selection = self.__included_folders_lb.curselection() # check if there is a selection if curr_selection: if messagebox.askyesno("Confirm Delete", "Are you want to delete this folder?"): index_to_del = curr_selection[0] self.__included_folders.pop(index_to_del) self.__app_config.set_included_folders(self.__curr_config, self.__included_folders) self.__included_folders_lb.delete(index_to_del) self.deselect_included_folder() def add_excluded_folder(self): """ add a folder to exclude in the backup, will ask user for a directory """ folder = filedialog.askdirectory(initialdir="/", title="Select Folder To Exclude") if folder: folder_path = Path(folder) self.__excluded_folders.append(folder_path) self.__excluded_folders_lb.insert(END, folder_path) self.__app_config.set_excluded_folders(self.__curr_config, self.__excluded_folders) def remove_selected_excluded_folder(self, *args): """ remove the currently selected item in the excluded folders ListBox, will ask the user to confirm """ curr_selection = self.__excluded_folders_lb.curselection() # check if there is a selection if curr_selection: if messagebox.askyesno("Confirm Delete", "Are you want to delete this folder?"): index_to_del = curr_selection[0] self.__excluded_folders.pop(index_to_del) self.__app_config.set_excluded_folders(self.__curr_config, self.__excluded_folders) self.__excluded_folders_lb.delete(index_to_del) self.deselect_excluded_folder() def set_backup_folder(self): """ sets the backup folder by asking the user for a base directory """ folder = filedialog.askdirectory(initialdir="/", title="Select Where To Backup To") if folder: self.__backup_location = Path(folder) self.__backup_folder_l.config(text=folder) self.__app_config.set_backup_path(self.__curr_config, self.__backup_location) def enable_gui(self): """ enable the gui buttons, run when a backup has completed """ self.__set_versions_to_keep.config(state=NORMAL) self.__inc_folder_bnt.config(state=NORMAL) self.__included_folders_lb.config(state=NORMAL) self.__excl_folder_bnt.config(state=NORMAL) self.__excluded_folders_lb.config(state=NORMAL) self.__backup_to_bnt.config(state=NORMAL) self.__use_tar.config(state=NORMAL) self.__backup_start_bnt.config(state=NORMAL) def disable_gui(self): """ disable the gui buttons, run when a backup is started """ self.__set_versions_to_keep.config(state=DISABLED) self.__inc_folder_bnt.config(state=DISABLED) self.__included_folders_lb.config(state=DISABLED) self.__excl_folder_bnt.config(state=DISABLED) self.__excluded_folders_lb.config(state=DISABLED) self.__backup_to_bnt.config(state=DISABLED) self.__use_tar.config(state=DISABLED) self.__backup_start_bnt.config(state=DISABLED) def progress_find_incr(self, finished=False): """ increment the progress bar for finding files by 1 or mark as finished :param finished: mark the progressbar as finished """ if finished: self.__progress.config(mode="determinate") self.__progress.config(value=0, maximum=self.__files_found) self.__statusbar.config(text=f"Found {self.__files_found} Files") else: self.__files_found += 1 self.__progress.config(value=self.__files_found) self.__statusbar.config( text=f"Searching For Files, Found {self.__files_found} Files") def progress_copy_incr(self): """ increment the progress bar for copying files by 1 or mark as finished """ self.__files_copied += 1 self.__progress.config(value=self.__files_copied) self.__statusbar.config( text=f"Copying Files {self.__files_copied} of {self.__files_found}" ) if self.__files_copied == self.__files_found: self.__app_config.set_last_backup(self.__curr_config, datetime.utcnow()) self.__last_backup_l.config( text= f"Last Known Backup: {self.__app_config.get_human_last_backup(self.__curr_config)}" ) self.__statusbar.config(text=f"Finished Copying Files") messagebox.showinfo(title="Finished Copying Files", message="Finished copying all found files") # reset counters self.__files_found = 0 self.__files_copied = 0 self.__progress.config(value=0, maximum=100) self.enable_gui() def start_backup(self): """ starts the backup """ if not self.__backup_location: # no backup location was selected messagebox.showwarning( title="Backup Location Not Selected", message="You did not select a backup location!") elif not self.__included_folders: # no folders where found to backup messagebox.showwarning( title="No Folders To Backup", message="You did not add any folders to backup!") else: # basic checks passed self.disable_gui() # prep for search of files self.__progress.config(mode="indeterminate") self.__statusbar.config(text=f"Searching For Files") self.__thread = BackupThread( self.__included_folders, self.__excluded_folders, self.__backup_location, self.__versions_to_keep, self.progress_find_incr, self.progress_copy_incr, self.handle_error_message, self.__use_tar_var.get()) # start the background backup thread so GUI wont appear frozen self.__thread.start() def show_about_popup(self): """ show the about popup """ messagebox.showinfo( "About", "simplebackup V" + __version__ + """ is cross-platform backup program written in python. This app was made by enchant97/Leo Spratt. It is licenced under GPL-3.0""") def show_update_popup(self): """ open the default webbrowser to the update url """ webbrowser.open(UPDATE_URL) def show_help_popup(self): messagebox.showinfo( "Welcome", """Welcome to simplebackup, here is some help to get you started: \nIncluding a folder to backup - Press the 'Include Folder' button to add a folder to backup - Remove a entry by clicking on the list below \nExcluding a folder from the backup - Press the 'Exclude Folder' button to skip a folder to backup - Remove a entry by clicking on the list below \nSetting where backups are stored - Click the 'Backup Folder' button to set where backups should be placed \nMultiple backup configs Use the 'Config' button in the titlebar to change varius settings like creating a new config \nVersions to keep This will be the number of backup to keep in the backup folder """) self.__app_config.show_help = False def handle_error_message(self, error_type: ERROR_TYPES): self.__statusbar.config(text="Failed") if error_type is ERROR_TYPES.NO_BACKUP_WRITE_PERMISION: messagebox.showerror("No Write Permission", ERROR_TYPES.NO_BACKUP_WRITE_PERMISION.value) elif error_type is ERROR_TYPES.NO_BACKUP_READ_PERMISION: messagebox.showerror("No Read Permission", ERROR_TYPES.NO_BACKUP_READ_PERMISION.value) elif error_type is ERROR_TYPES.NO_FILES_FOUND_TO_BACKUP: messagebox.showerror("No Files Found", ERROR_TYPES.NO_FILES_FOUND_TO_BACKUP.value) elif error_type is ERROR_TYPES.NO_BACKUP_PATH_FOUND: messagebox.showerror("No Backup Path Found", ERROR_TYPES.NO_BACKUP_PATH_FOUND.value) self.__progress.config(mode="determinate") self.enable_gui() def _layout(self): self.config(menu=self.__menu) self.__title_l.pack(fill=X, pady=10, padx=5) self.__curr_config_name_l.pack(fill=X, padx=5) self.__last_backup_l.pack(fill=X, padx=5) self.__set_versions_to_keep.pack(fill=X, padx=5) self.__versions_to_keep_l.pack(fill=X, padx=5) self.__inc_folder_bnt.pack(fill=X, padx=5) self.__included_folders_lb.pack(fill=X, padx=5) self.__excl_folder_bnt.pack(fill=X, padx=5) self.__excluded_folders_lb.pack(fill=X, padx=5) self.__backup_to_bnt.pack(fill=X, padx=5) self.__backup_folder_l.pack(fill=X, padx=5) self.__use_tar_l.pack(fill=X, padx=5) self.__use_tar.pack(fill=X, padx=5) self.__backup_start_bnt.pack(fill=X, padx=5) self.__progress.pack(fill=X) self.__statusbar.pack(side=BOTTOM, fill=X) self.wm_minsize(300, self.winfo_height()) self.wm_resizable(True, False)
class DownloadPopup(Toplevel): def __init__(self, master, info: dict, message_store: MessageStore, video_id: str = None): Toplevel.__init__(self, master) self.bind('<Escape>', lambda _: self.cancel()) self.bind('<Return>', lambda _: self.ok()) self.title('Get VOD') self.transient(master) self.grab_set() self.info: dict = info self.message_store: MessageStore = message_store self.chat_downloader: ChatDownloader = None self.updated_info: bool = False self.status_var = StringVar(value='...') self.content = Frame(self) self.content.pack(padx=20, pady=15) self.video_title_var = StringVar(value='') self.download_info_var = StringVar(value='') self.eta_var = StringVar(value='') Label(self.content, text='Enter a VOD URL or video ID:').pack(side=TOP, anchor=W, pady=(0, 5)) self.entry = Entry(self.content, width=50) self.entry.pack(side=TOP, padx=2, pady=(0, 5)) Label(self.content, textvariable=self.status_var).pack(side=TOP, anchor=W, pady=(0, 5)) self.progress_var = IntVar(value=0) self.progress = Progressbar(self.content, variable=self.progress_var, maximum=1) self.progress.pack(side=TOP, fill=X, padx=2) Label(self.content, textvariable=self.video_title_var).pack(side=TOP, anchor=W, pady=(0, 5)) Label(self.content, textvariable=self.download_info_var).pack(side=TOP, anchor=W, pady=(0, 5)) Label(self.content, textvariable=self.eta_var).pack(side=TOP, anchor=W, pady=(0, 5)) self.overwrite_cache_var = BooleanVar(value=False) self.overwrite_cache_check = Checkbutton(self.content, text='Overwrite cache', variable=self.overwrite_cache_var) self.overwrite_cache_check.pack(side=TOP, anchor=W, pady=(0, 5)) self.button = Button(self.content, text='OK', command=self.ok) self.button.pack(side=TOP) self.update() x_coord = self.master.winfo_x() + (self.master.winfo_width() // 2) - (self.winfo_width() // 2) y_coord = self.master.winfo_y() + (self.master.winfo_height() // 2) - (self.winfo_height() // 2) self.geometry(f'{self.winfo_width()}x{self.winfo_height()}+{x_coord}+{y_coord}') self.entry.focus_set() self.protocol('WM_DELETE_WINDOW', self.cancel) if video_id: self.entry.insert(0, video_id) self.overwrite_cache_check.focus_set() chat_filename: str = os.path.join(CACHE_FOLDER, f'chat-{video_id}.json') if not os.path.exists(chat_filename): self.ok() def cancel(self): if self.chat_downloader: self.chat_downloader.kill() self.info.clear() self.info.update({'title': 'Chat Player'}) self.destroy() def ok(self): self.button.config(state=DISABLED) self.overwrite_cache_check.config(state=DISABLED) self.status_var.set('Validating...') self.after(1, self.validate) def validate(self): video_id: str = self.entry.get() if 'http' in video_id or 'twitch.tv' in video_id: video_id = parse_url(video_id) if len(video_id) > 0 and video_exists(video_id): self.chat_downloader = ChatDownloader(video_id, overwrite_cache=self.overwrite_cache_var.get()) self.chat_downloader.start() self.after(1, self.download) else: self.status_var.set('Error: Invalid URL or video ID.') self.button.config(state=NORMAL) def download(self): if not self.chat_downloader.info: self.status_var.set('Getting info') self.after(100, self.download) elif not self.chat_downloader.messages: if not self.updated_info: self.status_var.set('Downloading chat') self.info.update(self.chat_downloader.info) self.video_title_var.set(self.info.get('title')) self.updated_info = True self.progress_var.set(self.chat_downloader.progress) self.download_info_var.set( f'{self.chat_downloader.num_messages} messages downloaded. ' f'Duration {self.chat_downloader.duration_done_str}/{self.chat_downloader.duration_str}.') self.eta_var.set(f'ETA: {self.chat_downloader.eta_str}') self.after(100, self.download) else: self.message_store.set_messages(self.chat_downloader.messages) self.destroy()
class Application(Frame): """Main class, handles GUI and starts logic""" def __init__(self, master=None, arguments=()): super().__init__(master) self._file_path = "" self._output_dir = "" self._same_dir = IntVar(value=1) # By default output path is the same as file path self._show_password = False self._db_type = IntVar(value=self.DB_TYPE_MARIADB()) self._master = master self.grid() try: self._create_widgets() except Exception as ex: messagebox.showerror("Error", ex.args[0]) self._same_dir_as_file() # Disable output button and it sets output dir at start self._convert_progressbar.grid_remove() # Hide loading bar at start if self._is_file_path_in_arguments(arguments): self._set_file_path(arguments[1]) def _is_file_path_in_arguments(self, arguments): """Method to check if arguments[1] (because arguments[0] is application path) is a file path """ if arguments.__len__() > 1: file_path = arguments[1] if path.isfile(file_path): extension = path.splitext(file_path)[1] if extension == ".mdb" or extension == ".accdb": return True return False def _create_widgets(self): """GUI building""" # File widgets self._filename_label = Label(self._master, width="22", anchor="e", text="Access File (*.mdb, *.accdb):") self._filename_label.grid(row=0, column=0) self._filename_path_label = Label(self._master, width="50", anchor="w", textvariable=self._file_path, bg="#cccccc") self._filename_path_label.grid(row=0, column=1) self._browse_file_button = Button(self._master, text="...", width="3", command=self._browse_file) self._browse_file_button.grid(row=0, column=2) # Password widget self._password_label = Label(self._master, width="22", anchor="e", text="Password (else leave empty):") self._password_label.grid(row=1, column=0) self._password_entry = Entry(self._master, width="58", show="*") self._password_entry.grid(row=1, column=1) self._password_show_image = PhotoImage(file=path.join(module_dir, "images\\watch_pwd.png")).subsample(8, 8) self._password_show_button = Button(self._master, width="3", command=self._show_hide_password, image=self._password_show_image) self._password_show_button.grid(row=1, column=2) # Checkbox widget self._same_dir_as_file_checkbox = Checkbutton(self._master, width="50", text="Same directory as source file", var=self._same_dir, command=self._same_dir_as_file) self._same_dir_as_file_checkbox.grid(row=2, column=1, pady=8) # Output widgets self._output_label = Label(self._master, width="22", anchor="e", text="Output directory:") self._output_label.grid(row=3, column=0) self._output_dir_label = Label(self._master, width="50", anchor="w", textvariable=self._file_path, bg="#cccccc") self._output_dir_label.grid(row=3, column=1) self._browse_dir_button = Button(self._master, text="...", width="3", command=self._browse_dir) self._browse_dir_button.grid(row=3, column=2) # Radio buttons for PostgreSQL or MySQL/MariaDB self._db_type_label = Label(self._master, width="22", anchor="e", text="Database type:") self._db_type_label.grid(row=4, column=0) self._db_type_frame = Frame(self._master) self._db_type_frame.grid(row=4, column=1, columnspan=2, pady=5) self._radio_button_postgres = Radiobutton(self._db_type_frame, text="PostgreSQL", var=self._db_type, value=self.DB_TYPE_POSTGRESQL(), width="13") self._radio_button_postgres.grid(row=0, column=0) self._radio_button_mariadb = Radiobutton(self._db_type_frame, text="MariaDB", var=self._db_type, value=self.DB_TYPE_MARIADB(), width="13") self._radio_button_mariadb.grid(row=0, column=1) self._radio_button_mysql = Radiobutton(self._db_type_frame, text="MySQL", var=self._db_type, value=self.DB_TYPE_MYSQL(), width="13") self._radio_button_mysql.grid(row=0, column=2) # Convert widget & progressbar self._convert_frame = Frame(self._master) self._convert_frame.grid(row=5, column=0, columnspan=2, pady=5) self._convert_button = Button(self._convert_frame, width="84", text="CREATE SQL FILE", command=self.convertSQL, state="disabled") self._convert_button.grid(row=0, column=0) self._convert_progressbar = Progressbar(self._convert_frame, length="512") self._convert_progressbar.grid(row=1, column=0) def convertSQL(self): """SQL file generator""" self._convert_progressbar.grid(row=1, column=0) # Show progressbar self._convert_progressbar["value"] = 0 self._master.config(cursor="wait") accessconnector = Accessconnector() try: driver = accessconnector.driver(self._file_path) con = accessconnector.con(driver, self._file_path, self._get_password()) self._convert_progressbar["value"] = 33 except (AccessConnectionError, ODBCDriverNotFoundError) as ex: self._master.config(cursor="") self._convert_progressbar["value"] = 100 messagebox.showerror("Error", ex) else: cur = con.cursor() database_name = StringDialog.ask_string(title_="Database name", prompt="Name for database:") if database_name is not None: if database_name == "": messagebox.showinfo("Error", "Database name field cannot be blank") else: self._convert_progressbar["value"] = 66 accesshandler = Accesshandler(cur) accesshandler.make_file(self._output_dir, database_name, self._db_type.get()) messagebox.showinfo("Completed", "SQL file generated successfully") cur.close() con.close() self._master.config(cursor="") self._convert_progressbar["value"] = 100 def _set_file_path(self, file_path): """Setter for file path, updates file path label and output dir based on _same_dir""" self._file_path = file_path self._filename_path_label.config(text=self._file_path) if self._is_same_dir() == 1: self._set_output_dir(path.dirname(self._file_path)) if self._file_path != "" and self._output_dir != "": # Enable convert button if it´s good to go self._convert_button.config(state="normal") self._update_gui_size() def _set_output_dir(self, output_dir): """Setter for output dir""" self._output_dir = output_dir self._output_dir_label.config(text=self._output_dir) if self._file_path != "" and self._output_dir != "": # Enable convert button if it´s good to go self._convert_button.config(state="normal") self._update_gui_size() def _update_gui_size(self): """Method for expanding or shrinking GUI based on the length of the paths""" min_size = 50 # Standard min-size on creating GUI for _filename_path_label ... size = max(self._file_path.__len__(), self._output_dir.__len__()) if size > min_size: self._filename_path_label.config(width=size) self._output_dir_label.config(width=size) self._same_dir_as_file_checkbox.config(width=size) self._password_entry.config(width=int((size * 58) / min_size)) self._convert_button.config(width=int((size * 84) / min_size)) self._convert_progressbar.config(length=int((size * 512) / min_size)) elif size <= min_size: self._filename_path_label.config(width=50) self._output_dir_label.config(width=50) self._same_dir_as_file_checkbox.config(width=50) self._password_entry.config(width=58) self._convert_button.config(width=84) self._convert_progressbar.config(length=512) def _same_dir_as_file(self): """Functionality for disabling or enabling browse output dir button and setting _output_dir based on _file_path """ if self._is_same_dir() == 1: self._set_output_dir(path.dirname(self._file_path)) self._browse_dir_button.config(state="disabled") else: self._browse_dir_button.config(state="normal") def _show_hide_password(self): """Show/Hide password by current _show_password value and updates it""" if self._show_password: self._password_entry.config(show="*") try: self._password_show_image = PhotoImage(file=path.join(module_dir, "images\\watch_pwd.png")).subsample(8, 8) except Exception as ex: messagebox.showerror("Error", ex) else: self._password_entry.config(show="") try: self._password_show_image = PhotoImage(file=path.join(module_dir, "images\\hide_pwd.png")).subsample(8, 8) except Exception as ex: messagebox.showerror("Error", ex) self._password_show_button.config(image=self._password_show_image) self._show_password = not self._show_password def _browse_file(self): """Browse file functionality""" file_path = askopenfilename(filetypes=[("Microsoft Access", ".mdb .accdb")]) if file_path != "": # If browse window is closed then don´t update self._set_file_path(file_path) def _browse_dir(self): """Browse dir functionality""" output_dir = askdirectory() if output_dir != "": # If browse window is closed then don´t update self._set_output_dir(output_dir) def _get_password(self): """Getter for password""" return self._password_entry.get() def _is_same_dir(self): """Getter for _same_dir""" return self._same_dir.get() @staticmethod def DB_TYPE_POSTGRESQL(): """POSTGRESQL constant""" return 1 @staticmethod def DB_TYPE_MARIADB(): """MARIADB constant""" return 2 @staticmethod def DB_TYPE_MYSQL(): """MYSQL constant""" return 3