Skip to content

Commit 5ddc805

Browse files
authored
Code/GUI updates
1 parent 8ffb552 commit 5ddc805

File tree

3 files changed

+126
-41
lines changed

3 files changed

+126
-41
lines changed

OneDriveExplorer/OneDriveExplorer.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import sys
23
import re
34
import io
@@ -6,7 +7,7 @@
67
import argparse
78

89
__author__ = "Brian Maloney"
9-
__version__ = "2022.02.09"
10+
__version__ = "2022.02.11"
1011
__email__ = "[email protected]"
1112

1213
ASCII_BYTE = rb" !\"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}\\\~\t"
@@ -70,8 +71,8 @@ def parse_onedrive(usercid, outfile, pretty):
7071

7172
folder_structure = {'Folder_UUID': '',
7273
'Object_UUID': dir_list[0],
73-
'Type': 'Folder',
74-
'Name': 'Root',
74+
'Type': 'Root',
75+
'Name': f.name,
7576
'Children': []
7677
}
7778

@@ -123,6 +124,11 @@ def parse_onedrive(usercid, outfile, pretty):
123124
else:
124125
json_object = json.dumps(folder_structure)
125126

127+
if not outfile:
128+
outfile = os.path.basename(f.name).split('.')[0]+"_OneDrive.json"
129+
file_extension = os.path.splitext(f.name)[1][1:]
130+
if file_extension == 'previous':
131+
outfile = os.path.basename(f.name).split('.')[0]+"_"+file_extension+"_OneDrive.json"
126132
output = open(outfile, 'w')
127133
output.write(json_object)
128134
sys.exit()
@@ -143,7 +149,7 @@ def main():
143149
print(banner)
144150
parser = argparse.ArgumentParser()
145151
parser.add_argument("-f", "--file", help="<UserCid>.dat file to be parsed")
146-
parser.add_argument("-o", "--outfile", help="File name to save json representation to. When pressent, overrides default name", default="OneDrive.json")
152+
parser.add_argument("-o", "--outfile", help="File name to save json representation to. When pressent, overrides default name")
147153
parser.add_argument("--pretty", help="When exporting to json, use a more human readable layout. Default is FALSE", action='store_true')
148154

149155
if len(sys.argv) == 1:

OneDriveExplorer/OneDriveExplorer_GUI.py

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import threading
1212

1313
__author__ = "Brian Maloney"
14-
__version__ = "2022.02.09"
14+
__version__ = "2022.02.11"
1515
__email__ = "[email protected]"
1616

1717

@@ -84,6 +84,7 @@ def sync_windows(self, event=None):
8484
String = namedtuple("String", ["s", "offset"])
8585
uuid4hex = re.compile(b'{[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}}', re.I)
8686
found = []
87+
folder_structure = []
8788

8889
if getattr(sys, 'frozen', False):
8990
# If the application is run as a bundle, the PyInstaller bootloader
@@ -148,6 +149,7 @@ def folder_search(dict_list, input, duuid, added):
148149

149150
def clear_all():
150151
tv.delete(*tv.get_children())
152+
file_menu.entryconfig("Unload all folders", state='disable')
151153

152154

153155
def progress(total, count, ltext):
@@ -157,12 +159,14 @@ def progress(total, count, ltext):
157159

158160

159161
def parse_onederive(usercid):
160-
clear_all()
162+
# clear_all()
161163
details.config(state='normal')
162164
details.delete('1.0', tk.END)
163165
details.config(state='disable')
164166
menubar.entryconfig("File", state="disabled")
165167
menubar.entryconfig("Tools", state="disabled")
168+
search_entry.configure(state="disabled")
169+
btn.configure(state="disabled")
166170
dir_list = []
167171
misfits = []
168172
with open(usercid, 'rb') as f:
@@ -185,8 +189,8 @@ def parse_onederive(usercid):
185189

186190
folder_structure = {'Folder_UUID': '',
187191
'Object_UUID': dir_list[0],
188-
'Type': 'Folder',
189-
'Name': 'Root',
192+
'Type': 'Root',
193+
'Name': f.name,
190194
'Children': []
191195
}
192196

@@ -227,11 +231,30 @@ def parse_onederive(usercid):
227231
count += 1
228232
progress(count, total, 'Adding missing files/folders')
229233

234+
pb.configure(mode='indeterminate')
235+
value_label['text'] = "Building tree. Please wait..."
236+
pb.start()
237+
parent_child(folder_structure)
238+
pb.stop()
239+
pb.configure(mode='determinate')
240+
230241
menubar.entryconfig("File", state="normal")
231242
menubar.entryconfig("Tools", state="normal")
232-
parent_child(folder_structure)
243+
search_entry.configure(state="normal")
244+
btn.configure(state="normal")
245+
246+
json_object = json.dumps(folder_structure)
247+
file_extension = os.path.splitext(f.name)[1][1:]
248+
if file_extension == 'previous':
249+
output = open(os.path.basename(f.name).split('.')[0]+"_"+file_extension+"_OneDrive.json", 'w')
250+
else:
251+
output = open(os.path.basename(f.name).split('.')[0]+"_OneDrive.json", 'w')
252+
output.write(json_object)
253+
output.close()
233254
pb['value'] = 0
234255
value_label['text'] = 'Complete!'
256+
if len(tv.get_children()) > 0:
257+
file_menu.entryconfig("Unload all folders", state='normal')
235258

236259

237260
def parent_child(d, parent_id=None):
@@ -242,7 +265,10 @@ def parent_child(d, parent_id=None):
242265
for c in d['Children']:
243266
# Here we create a new row object in the TreeView and pass its return value for recursion
244267
# The return value will be used as the argument for the first parameter of this same line of code after recursion
245-
parent_child(c, tv.insert(parent_id, "end", text=c['Name'], values=(c['Folder_UUID'], c['Object_UUID'], c['Name'], c['Type'], len(c['Children']))))
268+
if len(c['Children']) == 0:
269+
parent_child(c, tv.insert(parent_id, "end", text=c['Name'], values=(c['Folder_UUID'], c['Object_UUID'], c['Name'], c['Type'], len(c['Children']))))
270+
else:
271+
parent_child(c, tv.insert(parent_id, 0, text=c['Name'], values=(c['Folder_UUID'], c['Object_UUID'], c['Name'], c['Type'], len(c['Children']))))
246272

247273

248274
def selectItem(a):
@@ -270,6 +296,22 @@ def open_dat():
270296
threading.Thread(target=parse_onederive, args=(filename,), daemon=True).start()
271297

272298

299+
def import_json():
300+
filename = filedialog.askopenfile(initialdir="/",
301+
title="Import JSON",
302+
filetypes=(("OneDrive dat file", "*.json"),))
303+
304+
if filename:
305+
# clear_all()
306+
details.config(state='normal')
307+
details.delete('1.0', tk.END)
308+
details.config(state='disable')
309+
parent_child(json.load(filename))
310+
filename.close()
311+
if len(tv.get_children()) > 0:
312+
file_menu.entryconfig("Unload all folders", state='normal')
313+
314+
273315
def save_settings():
274316
menu_data['theme'] = ttk.Style().theme_use()
275317
with open("ode.settings", "w") as jsonfile:
@@ -286,6 +328,55 @@ def fixed_map(option):
286328
if elm[:2] != ("!disabled", "!selected")]
287329

288330

331+
def do_popup(event):
332+
try:
333+
curItem = tv.identify_row(event.y)
334+
values = tv.item(curItem, 'values')
335+
popup = tk.Menu(root, tearoff=0)
336+
# popup.add_command(label="Remove OneDrive Folder" + (' '*10), command=lambda: del_folder(curItem))
337+
if values[3] == 'Root':
338+
popup.add_command(label="Remove OneDrive Folder", command=lambda: del_folder(curItem))
339+
popup.add_separator()
340+
else:
341+
popup.add_command(label="Copy", command=lambda: copy_item(values))
342+
if values[3] == 'Folder' or values[3] == 'Root':
343+
if values[3] == 'Folder':
344+
popup.add_separator()
345+
popup.add_command(label="Expand folders", command=lambda: open_children(curItem), accelerator="Alt+Down")
346+
popup.add_command(label="Collapse folders", command=lambda: close_children(curItem), accelerator="Alt+Up")
347+
popup.tk_popup(event.x_root, event.y_root)
348+
finally:
349+
popup.grab_release()
350+
351+
352+
def del_folder(iid):
353+
tv.delete(iid)
354+
details.config(state='normal')
355+
details.delete('1.0', tk.END)
356+
details.config(state='disable')
357+
if len(tv.get_children()) == 0:
358+
file_menu.entryconfig("Unload all folders", state='disable')
359+
360+
361+
def open_children(parent):
362+
tv.item(parent, open=True) # open parent
363+
for child in tv.get_children(parent):
364+
open_children(child) # recursively open children
365+
366+
367+
def close_children(parent):
368+
tv.item(parent, open=False) # close parent
369+
for child in tv.get_children(parent):
370+
close_children(child) # recursively close children
371+
372+
373+
def copy_item(values):
374+
line = f'Name: {values[2]}\nType: {values[3]}\nFolder_UUID: {values[0]}\nObject_UUID: {values[1]}'
375+
if values[3] == 'Folder':
376+
line += f'\n\n# Children: {values[4]}'
377+
root.clipboard_append(line)
378+
379+
289380
root = ThemedTk()
290381
ttk.Style().theme_use(menu_data['theme'])
291382
root.title(f'OneDriveExplorer v{__version__}')
@@ -314,8 +405,12 @@ def fixed_map(option):
314405
submenu.entryconfig(submenu.index(ttk.Style().theme_use()), background='grey'),
315406
save_settings()])
316407

317-
file_menu.add_command(label="Load <UsreCid>.dat", command=lambda: open_dat(), accelerator="Ctrl+O")
408+
file_menu.add_command(label="Load <UsreCid>.dat" + (' '*10), command=lambda: open_dat(), accelerator="Ctrl+O")
409+
file_menu.add_command(label="Import JSON", command=lambda: import_json())
410+
file_menu.add_command(label="Unload all folders", command=lambda: clear_all(), accelerator="Alt+0")
411+
file_menu.add_separator()
318412
file_menu.add_command(label="Exit", command=lambda: quit(root))
413+
file_menu.entryconfig("Unload all folders", state='disable')
319414
tool_menu.add_cascade(label="Skins",
320415
menu=submenu)
321416
menubar.add_cascade(label="File",
@@ -379,7 +474,11 @@ def fixed_map(option):
379474
pw.grid(row=1, column=0, columnspan=3, sticky="nsew")
380475

381476
tv.bind('<<TreeviewSelect>>', selectItem)
477+
tv.bind("<Button-3>", do_popup)
478+
tv.bind('<Alt-Down>', lambda event=None: open_children(tv.selection()))
479+
tv.bind('<Alt-Up>', lambda event=None: close_children(tv.selection()))
382480
root.bind('<Control-o>', lambda event=None: open_dat())
481+
root.bind('<Alt-0>', lambda event=None: clear_all())
383482
details.bind('<Key>', lambda a: "break")
384483
details.bind('<Button>', lambda a: "break")
385484
details.bind('<Motion>', lambda a: "break")

README.md

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,24 @@
1-
# OneDriveExplorer Summary
2-
1+
# OneDriveExplorer Summary:
32
OneDriveExplorer is a command line and GUI based application for reconstructing the folder structure of OneDrive from the `<UserCid>.dat` and `<UserCid>.dat.previous` file.
4-
5-
## Usage
6-
3+
# Usage:
74
## Command line
8-
9-
![cmd_help](./Images/cmd_help.png)
5+
![](./Images/cmd_help.png)
106

117
To use OneDriveExporer, simply provide the `.\<UserCid>.dat` file to the `-f` argument
12-
13-
```bash
14-
OneDriveExplorer.py -f business1\d1a7c039-6175-4ddb-bcdb-a8de45cf1678.dat
15-
```
8+
> OneDriveExplorer.py -f business1\d1a7c039-6175-4ddb-bcdb-a8de45cf1678.dat
169
1710
OneDriveExplorer will produce a JSON file called OneDrive.json containing the folder structure. The `--pretty` option can be used to output the JSON into a more human readable layout.
18-
![json](./Images/json.png)
19-
11+
![](./Images/json.png)
2012
## GUI
21-
2213
The GUI consists of two panes: the folder structure on the left and details on the right. By clicking on one of the entries in the left pane, the details pane will populate with various data such as name, whether it is a file or folder, UUIDs and the number of children, if any.
2314

2415
To use the GUI, ttktheme package needs to installed. You can do this with the provided requirements.txt file as follows:
16+
> pip install -r requirements.txt
2517
26-
```bash
27-
pip3 install -r requirements.txt
28-
```
29-
30-
![gui](./Images/gui.png)
31-
32-
## File location
33-
34-
The default file location of the `.dat` files are:
35-
36-
- Personal: `C:\Users\<USERNAME>\AppData\Local\Microsoft\OneDrive\settings\Personal\<UserCid.dat>`
37-
- Business: `C:\Users\<USERNAME>\AppData\Local\Microsoft\OneDrive\settings\Business1\<UserCid.dat>`
38-
39-
## Todo
18+
![](./Images/gui.png)
4019

20+
# Todo
4121
- [x] Add support for OneDrive personal
4222
- [x] GUI not populating correctly when opening different dat file
43-
- [ ] Load multiple files in GUI
44-
- [ ] Performance improvements
23+
- [x] Load multiple files in GUI
24+
- [ ] Performance improvements

0 commit comments

Comments
 (0)