325 lines
9.5 KiB
Python
325 lines
9.5 KiB
Python
|
|
||
|
# This file was generated by the Tkinter Designer by Parth Jadhav
|
||
|
# https://github.com/ParthJadhav/Tkinter-Designer
|
||
|
# And modified by Mizuki Zou
|
||
|
# This was not made with quality in mind, but rather as a quick and dirty GUI. Keep this in mind while using it.
|
||
|
|
||
|
|
||
|
from pathlib import Path
|
||
|
from shutil import which
|
||
|
import os
|
||
|
import json
|
||
|
import time
|
||
|
import subprocess
|
||
|
import threading
|
||
|
|
||
|
# from tkinter import *
|
||
|
# Explicit imports to satisfy Flake8
|
||
|
from tkinter import Tk, Canvas, Entry, Text, Button, PhotoImage, filedialog, messagebox, Listbox, LEFT, BOTH, Scrollbar, RIGHT, END, HORIZONTAL, Toplevel
|
||
|
from tkinter.ttk import Progressbar
|
||
|
from tkinter.filedialog import askopenfilename
|
||
|
|
||
|
|
||
|
OUTPUT_PATH = Path(__file__).parent
|
||
|
ASSETS_PATH = OUTPUT_PATH / Path(r".")
|
||
|
|
||
|
config_path = ""
|
||
|
target_path = ""
|
||
|
|
||
|
windows_paths = ["C:\\Progam Files\\File Time Machine\\ftm.exe", "C:\\Program Files (x86)\\File Time Machine\\ftm.exe"]
|
||
|
path_windows = ""
|
||
|
|
||
|
if os.path.exists(windows_paths[0]):
|
||
|
path_windows = windows_paths[0]
|
||
|
elif os.path.exists(windows_paths[1]):
|
||
|
path_windows = windows_paths[1]
|
||
|
|
||
|
platform = os.name # nt for Windows, posix for Linux.
|
||
|
|
||
|
def relative_to_assets(path: str) -> Path:
|
||
|
return ASSETS_PATH / Path(path)
|
||
|
|
||
|
def select_dir_or_file(dir: bool): # If dir, we know it is the target path. Otherwise, it is the config file
|
||
|
global config_path, target_path, exists
|
||
|
if not exists:
|
||
|
messagebox.showerror("No binary", "FTM binary not found! You need to install it before using the gui.")
|
||
|
return
|
||
|
|
||
|
if dir:
|
||
|
messagebox.showwarning("Warning!", "This software is NOT stable, and will probably result in data loss if you use it!")
|
||
|
folder_selected = filedialog.askdirectory()
|
||
|
target_path = folder_selected
|
||
|
if folder_selected != "":
|
||
|
print(folder_selected)
|
||
|
window.title(folder_selected)
|
||
|
|
||
|
if not os.path.isdir(folder_selected+"/.time"):
|
||
|
if messagebox.askquestion('Config file','This folder is not currently being tracked, do you want to begin tracking it? A config folder will be created for you, and default settings will be applied. (No hashing, compression level 5, multithreading enabled)'):
|
||
|
os.mkdir(folder_selected+"/.time")
|
||
|
print("Starting to track "+folder_selected)
|
||
|
if platform == "posix":
|
||
|
config_path = folder_selected+"/.time/gui-config.json"
|
||
|
else:
|
||
|
config_path = folder_selected+"\\.time\\gui-config.json"
|
||
|
config_file = open(config_path, 'w')
|
||
|
print("Writing config to "+str(config_file))
|
||
|
config_file.write('''[
|
||
|
{
|
||
|
"folder_path": "'''+folder_selected+'''",
|
||
|
"get_hashes": false,
|
||
|
"thread_count": 0,
|
||
|
"brotli_compression_level": 5,
|
||
|
"snapshot_mode": "fastest",
|
||
|
"its_my_fault_if_i_lose_data": true
|
||
|
}
|
||
|
]''')
|
||
|
config_file.close()
|
||
|
else:
|
||
|
if not os.path.exists(folder_selected+"/.time/gui-config.json"):
|
||
|
print(folder_selected+"/.time/gui-config.json")
|
||
|
messagebox.showinfo("No config", "Could not find a config file, please specify one")
|
||
|
else:
|
||
|
config_path = folder_selected+"/.time/gui-config.json"
|
||
|
get_snap_list()
|
||
|
else:
|
||
|
config_path = askopenfilename()
|
||
|
print(config_path)
|
||
|
|
||
|
def get_snap_list():
|
||
|
global listbox, target_path
|
||
|
listbox.delete(0, END)
|
||
|
print(target_path+'/.time/snapshots.json')
|
||
|
if os.path.exists(target_path+'/.time/snapshots.json'):
|
||
|
with open(target_path+'/.time/snapshots.json') as f:
|
||
|
d = json.load(f)
|
||
|
for i in range(len(d)):
|
||
|
print(d[i]["date_created"])
|
||
|
listbox.insert(END, d[i]["date_created"])
|
||
|
else:
|
||
|
messagebox.showinfo("No Snapshots", "Did not find any snapshots to list.")
|
||
|
|
||
|
def create_snapshot():
|
||
|
global config_path
|
||
|
if config_path == "":
|
||
|
messagebox.showerror("Select folder", "You need to select a folder before you can create a snapshot!")
|
||
|
return
|
||
|
output = ""
|
||
|
progress_window = Toplevel()
|
||
|
progress_window.resizable(width=False, height=False)
|
||
|
progress_window.title("Creating snapshot...")
|
||
|
progress_window.geometry("300x100")
|
||
|
|
||
|
# Create a progress bar in the new window
|
||
|
progress = Progressbar(progress_window, orient=HORIZONTAL, length=280, mode='indeterminate')
|
||
|
progress.pack(pady=20)
|
||
|
if platform == "posix":
|
||
|
print("Running command 'ftm -c "+config_path+"'")
|
||
|
progress.start()
|
||
|
p1 = subprocess.Popen(['ftm', '-c', config_path], stdout=subprocess.PIPE)
|
||
|
else:
|
||
|
print("Running command '"+path_windows+" -c "+config_path+"'")
|
||
|
progress.start()
|
||
|
p1 = subprocess.Popen([path_windows, '-c', config_path], stdout=subprocess.PIPE)
|
||
|
# p1 = subprocess.Popen(['sleep', '3'], stdout=subprocess.PIPE)
|
||
|
output = p1.communicate()[0]
|
||
|
print(output)
|
||
|
progress.stop()
|
||
|
progress_window.destroy()
|
||
|
if "No files changed" in str(output):
|
||
|
messagebox.showwarning("No changed files", "There were no changed files, so I cannot take a snapshot!")
|
||
|
get_snap_list()
|
||
|
|
||
|
def restore_snapshot():
|
||
|
global listbox
|
||
|
# print(listbox.curselection()[0])
|
||
|
selection = listbox.curselection()[0]+1
|
||
|
if listbox.curselection() == ():
|
||
|
messagebox.showerror("No snapshot", "No snapshot is selected!")
|
||
|
return
|
||
|
progress_window = Toplevel()
|
||
|
progress_window.resizable(width=False, height=False)
|
||
|
progress_window.title("Restoring snapshot")
|
||
|
progress_window.geometry("300x100")
|
||
|
|
||
|
# Create a progress bar in the new window
|
||
|
progress = Progressbar(progress_window, orient=HORIZONTAL, length=280, mode='indeterminate')
|
||
|
progress.pack(pady=20)
|
||
|
progress.start()
|
||
|
print("Running 'ftm -c "+config_path+" restore --restore-index "+str(selection)+"'")
|
||
|
p1 = subprocess.Popen(
|
||
|
['ftm', '-c', config_path, 'restore', '--restore-index', str(selection)], stdout=subprocess.PIPE)
|
||
|
output = p1.communicate()[0]
|
||
|
print(output)
|
||
|
progress.stop()
|
||
|
progress_window.destroy()
|
||
|
if "Finished restoring" not in str(output):
|
||
|
messagebox.showerror("Error", "There was an issue restoring a snapshot! Error: "+str(output))
|
||
|
|
||
|
window = Tk()
|
||
|
|
||
|
window.geometry("443x428")
|
||
|
window.configure(bg = "#313244")
|
||
|
|
||
|
if platform == "posix":
|
||
|
exists = which("ftm")
|
||
|
else:
|
||
|
exists = os.path.exists(path_windows)
|
||
|
|
||
|
canvas = Canvas(
|
||
|
window,
|
||
|
bg = "#313244",
|
||
|
height = 428,
|
||
|
width = 443,
|
||
|
bd = 0,
|
||
|
highlightthickness = 0,
|
||
|
relief = "ridge"
|
||
|
)
|
||
|
|
||
|
canvas.place(x = 0, y = 0)
|
||
|
canvas.create_text(
|
||
|
90.0,
|
||
|
19.0,
|
||
|
anchor="nw",
|
||
|
text="FTM-GUI",
|
||
|
fill="#CDD6F4",
|
||
|
font=("Jost Regular", 32 * -1)
|
||
|
)
|
||
|
|
||
|
if exists:
|
||
|
canvas.create_rectangle(
|
||
|
228.0,
|
||
|
20.0,
|
||
|
416.0,
|
||
|
70.0,
|
||
|
fill="#A6E3A1",
|
||
|
outline="")
|
||
|
|
||
|
canvas.create_text(
|
||
|
260.0,
|
||
|
32.0,
|
||
|
anchor="nw",
|
||
|
text="Found ftm binary!",
|
||
|
fill="#000000",
|
||
|
font=("Jost Regular", 15 * -1)
|
||
|
)
|
||
|
else:
|
||
|
canvas.create_rectangle(
|
||
|
228.0,
|
||
|
20.0,
|
||
|
416.0,
|
||
|
70.0,
|
||
|
fill="#e78284",
|
||
|
outline="")
|
||
|
|
||
|
canvas.create_text(
|
||
|
250.0,
|
||
|
32.0,
|
||
|
anchor="nw",
|
||
|
text="No ftm binary found!",
|
||
|
fill="#000000",
|
||
|
font=("Jost Regular", 15 * -1)
|
||
|
)
|
||
|
|
||
|
listbox = Listbox(window, bg="#9399B2", selectmode='single')
|
||
|
scrollbar = Scrollbar(window, bg="#9399B2")
|
||
|
# Create scrollbox for list of snapshots
|
||
|
listbox.place(x=29, y=154, width=387, height=209) # Listbox within the rectangle
|
||
|
scrollbar.place(x=416, y=154, height=209) # Scrollbar on the right side of the Listbox
|
||
|
|
||
|
# Attach the Listbox to the Scrollbar
|
||
|
# for values in range(100):
|
||
|
# listbox.insert(END, values)
|
||
|
|
||
|
listbox.config(yscrollcommand=scrollbar.set)
|
||
|
scrollbar.config(command=listbox.yview)
|
||
|
canvas.create_rectangle( # Scrollbox rectangle
|
||
|
29.0,
|
||
|
154.0,
|
||
|
416.0,
|
||
|
363.0,
|
||
|
fill="#9399B2",
|
||
|
outline="")
|
||
|
|
||
|
canvas.create_text(
|
||
|
29.0,
|
||
|
128.0,
|
||
|
anchor="nw",
|
||
|
text="Available snapshots",
|
||
|
fill="#CDD6F4",
|
||
|
font=("Jost Regular", 15 * -1)
|
||
|
)
|
||
|
|
||
|
button_image_1 = PhotoImage(
|
||
|
file=relative_to_assets("button_1.png"))
|
||
|
button_1 = Button(
|
||
|
image=button_image_1,
|
||
|
borderwidth=0,
|
||
|
highlightthickness=0,
|
||
|
command=lambda: threading.Thread(target=restore_snapshot, daemon=True).start(),
|
||
|
relief="flat"
|
||
|
)
|
||
|
button_1.place(
|
||
|
x=26.0,
|
||
|
y=376.0,
|
||
|
width=196.0,
|
||
|
height=27.0
|
||
|
)
|
||
|
|
||
|
button_image_2 = PhotoImage(
|
||
|
file=relative_to_assets("button_2.png"))
|
||
|
button_2 = Button(
|
||
|
image=button_image_2,
|
||
|
borderwidth=0,
|
||
|
highlightthickness=0,
|
||
|
command=lambda: threading.Thread(target=create_snapshot, daemon=True).start(),
|
||
|
relief="flat"
|
||
|
)
|
||
|
button_2.place(
|
||
|
x=225.0,
|
||
|
y=376.0,
|
||
|
width=194.0,
|
||
|
height=26.0
|
||
|
)
|
||
|
|
||
|
button_image_3 = PhotoImage(
|
||
|
file=relative_to_assets("button_3.png"))
|
||
|
button_3 = Button(
|
||
|
image=button_image_3,
|
||
|
borderwidth=0,
|
||
|
highlightthickness=0,
|
||
|
command=lambda: select_dir_or_file(True),
|
||
|
relief="flat"
|
||
|
)
|
||
|
button_3.place(
|
||
|
x=17.0,
|
||
|
y=86.0,
|
||
|
width=194.0,
|
||
|
height=26.0
|
||
|
)
|
||
|
|
||
|
button_image_4 = PhotoImage(
|
||
|
file=relative_to_assets("button_4.png"))
|
||
|
button_4 = Button(
|
||
|
image=button_image_4,
|
||
|
borderwidth=0,
|
||
|
highlightthickness=0,
|
||
|
command=lambda: select_dir_or_file(False),
|
||
|
relief="flat"
|
||
|
)
|
||
|
button_4.place(
|
||
|
x=225.0,
|
||
|
y=86.0,
|
||
|
width=194.0,
|
||
|
height=26.0
|
||
|
)
|
||
|
|
||
|
image_image_1 = PhotoImage(
|
||
|
file=relative_to_assets("image_1.png"))
|
||
|
image_1 = canvas.create_image(
|
||
|
42.0,
|
||
|
42.0,
|
||
|
image=image_image_1
|
||
|
)
|
||
|
window.resizable(False, False)
|
||
|
window.mainloop()
|