Skip to content

Instantly share code, notes, and snippets.

@hellocatfood
Last active January 16, 2023 15:25
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hellocatfood/63b04fd011233d56a09a25c2cec0c6c7 to your computer and use it in GitHub Desktop.
Save hellocatfood/63b04fd011233d56a09a25c2cec0c6c7 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# Author: David Huss
# Website: http://blog.atoaudiovisual.com
# Usage instructions http://blog.atoav.com/2013/09/restore-broken-deja-dup-backup-hand/
# Modified by Antonio Roberts to preserve folder structure
import Tkinter as tk
from tkFileDialog import *
import sys, os
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
toolbar = tk.Frame(self)
toolbar.pack(side="left", fill="y")
b1 = tk.Button(self, text="open", command=self.open)
b2 = tk.Button(self, text="join!", command=self.save)
b1.pack(in_=toolbar, side="top")
b2.pack(in_=toolbar, side="top")
self.title("duplicity_joiner")
self.scrollbar = tk.Scrollbar(self)
self.scrollbar.pack(side="right", fill="y")
self.text = tk.Text(self, wrap="word", bg="#151515", bd="10", padx=20)
self.text.pack(side="top", fill="both", expand=True)
self.text.tag_configure("stderr", foreground="#b22222")
self.text.tag_configure("stdout", foreground="#808080")
self.text.tag_configure("green", foreground="#00DD00")
sys.stdout = TextRedirector(self.text, "stdout")
sys.stderr = TextRedirector(self.text, "stderr")
sys.green = TextRedirector(self.text, "green")
self.text.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.text.yview)
self.directory = None
self.savedirectory = None
print "This is the duplicity file joiner, which will try to join the files of an duplicity multivol_snapshot."
print
print "Note: this is not professional software, this is something I put up when I thought my backup will be lost otherwise. As far as I tested it works (it worked for me). I wont guarantee it works for you."
print
print "Good Luck!"
print
def open(self):
if self.directory is None and self.savedirectory is None:
self.directory = askdirectory(title="Open the folder containing the files to join", mustexist=True)
if len(self.directory) != 0:
sys.green.write("Selected for joining: "+self.directory+"\n")
self.savedirectory = askdirectory(title="Select where to save the resaved "+os.path.basename(self.directory))
if len(self.savedirectory) != 0:
sys.green.write("Selected for saving: "+self.savedirectory+"\n\n")
print "Press Join"
print
else:
sys.stderr.write("Cancled Selection\n")
self.savedirectory = None
self.directory = None
else:
sys.stderr.write("Cancled Selection\n")
self.directory = None
self.savedirectory = None
def save(self):
if self.savedirectory is not None and self.directory is not None:
self.find_leafes(self.directory)
self.directory = None
self.savedirectory = None
else:
sys.stderr.write("Select Input und Outputfile first: press open\n")
def find_leafes(self, directory):
print "Scanning for leaf directories ..."
for i, x in enumerate(os.walk(directory)):
if not x[1]:
self.appendfile(x[0], self.savedirectory, i)
#sys.green.write("Sucess! Saved "+os.path.basename(x[0])+"\n\n")
print
print
self.text.see("end")
self.update_idletasks()
sys.green.write("Saved all leafes\n\n\n")
print
def sortlist(self, liste):
for i, entry in enumerate(liste):
liste[i] = int(entry)
liste.sort(key=int)
return liste
def appendfile(self, path, selecteddirectory, pos):
tempfiles = []
filename = os.path.basename(path)
splitpath = os.path.dirname(path).split("\\", 1)
rootpath = os.path.basename(splitpath[0])
print "rootpath: "+rootpath
print "filename: "+filename
filepath = splitpath[0]
savepath = selecteddirectory+"/"+filepath
print "savepath: "+savepath
print
fragments = self.sortlist(os.listdir(path))
collection = []
if not os.path.exists(savepath):
os.makedirs(savepath)
for fragment in fragments:
with open(path+"/"+str(fragment),"rb") as f:
collection.append(f.read())
with open(savepath+"/"+filename, "wb") as o:
for part in collection:
o.write(part)
print "Saved ["+str(pos)+"]: "+filename+" in directory "+savepath
print
self.text.see("end")
self.update_idletasks()
def stellen(self, number):
return len(str(number).replace('.', ''))
class TextRedirector(object):
def __init__(self, widget, tag="stdout"):
self.widget = widget
self.tag = tag
def write(self, str):
self.widget.configure(state="normal")
self.widget.insert("end", str, (self.tag,))
self.widget.configure(state="disabled")
app = ExampleApp()
app.mainloop()
@Brawn1
Copy link

Brawn1 commented Dec 8, 2016

Thank you,

this program is awesome. 👍

@shijithpk
Copy link

thanks for this Antonio, got back two months of work with this script!

@Sanket758
Copy link

This is really awesome! I wish I could give 100 stars to this script.

@LucasCardim
Copy link

please teach me how to use this! the website is no longer available

@hellocatfood
Copy link
Author

@LucasCardim

please teach me how to use this! the website is no longer available

I haven't used this script in a long time so best to consult this snapshot of the original website https://web.archive.org/web/20160506043217/http://blog.atoav.com/2013/09/restore-broken-deja-dup-backup-hand/

@joelgraff
Copy link

joelgraff commented Jan 14, 2023

Just came acros this as I attempt to repair my own borken backup. The codebase didn't work with Python3, so I copied this code and created a repo here: https://github.com/joelgraff/duplicity-stitcher.git

@hellocatfood
Copy link
Author

@joelgraff brilliant, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment