Skip to content

Commit b75f428

Browse files
Initial Commit
0 parents  commit b75f428

File tree

5 files changed

+564
-0
lines changed

5 files changed

+564
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
DAZFix/__pycache__
2+
__pycache__
3+
4+
# PyInstaller files
5+
/build
6+
/dist
7+
*.spec
8+
9+
# stores paths on user's machine
10+
*.pkl

DAZFix/LibraryFix.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Spyder Editor
4+
5+
This is a temporary script file.
6+
"""
7+
8+
import os
9+
import sys
10+
import shutil
11+
import zlib
12+
13+
import globals
14+
15+
16+
def log_to_ui(text):
17+
globals.ui_object.ui.txtEditLog.insertPlainText(text + "\n")
18+
19+
# Scroll to the bottom after adding the log text
20+
scrollBar = globals.ui_object.ui.txtEditLog.verticalScrollBar()
21+
globals.ui_object.ui.txtEditLog.verticalScrollBar().setValue(scrollBar.maximum())
22+
23+
24+
class HaltException(Exception): pass
25+
26+
27+
def move_to_new_home(dupe_dirs_folder, dest_full_path, root):
28+
print(f'Moving objects into {dest_full_path}')
29+
for item in dupe_dirs_folder:
30+
dupe_folder_path = os.path.join(root, item)
31+
32+
# Don't do anything to the folder we're keeping!
33+
if dest_full_path in dupe_folder_path:
34+
# print(f"SKIPPING: {dupe_folder_path}")
35+
continue
36+
37+
# Move the files into the chosen directory
38+
# subfolder_alone = os.path.basename(os.path.normpath(dupe_folder_path))
39+
objects_within_folder = os.listdir(dupe_folder_path)
40+
41+
for obj in objects_within_folder:
42+
source = os.path.join(dupe_folder_path, obj)
43+
destination = os.path.join(dest_full_path, obj)
44+
print(f'Moving {obj} => {destination}');
45+
shutil.move(source, destination)
46+
47+
# Now that all the files are moved out, delete the directory
48+
os.rmdir(dupe_folder_path)
49+
50+
def make_archive(source, destination):
51+
base = os.path.basename(destination)
52+
name = base.split('.')[0]
53+
format = base.split('.')[1]
54+
archive_from = os.path.dirname(source)
55+
archive_to = os.path.basename(source.strip(os.sep))
56+
shutil.make_archive(name, format, archive_from, archive_to)
57+
shutil.move('%s.%s'%(name,format), destination)
58+
59+
60+
61+
def fix_libraries(backup_first, backup_zip_path, daz_default_library_path, daz_user_library_path):
62+
log_to_ui("Fixing libraries...")
63+
if globals.process_running:
64+
return
65+
66+
globals.process_running = True
67+
68+
default_path_directories = [x[0] for x in os.walk(daz_default_library_path)]
69+
default_lowered = [x.casefold() for x in default_path_directories]
70+
default_parent_dropped = [x.replace(daz_default_library_path,'') for x in default_path_directories]
71+
default_lowered_and_parent_dropped = [x.replace(daz_default_library_path,'').casefold() for x in default_path_directories]
72+
73+
74+
user_path_directories = [x[0] for x in os.walk(daz_user_library_path)]
75+
user_lowered = [x.casefold() for x in user_path_directories]
76+
user_parent_dropped = [x.replace(daz_user_library_path,'') for x in user_path_directories]
77+
user_lowered_and_parent_dropped = [x.replace(daz_user_library_path,'').casefold() for x in user_path_directories]
78+
79+
user_library = os.walk(daz_user_library_path, topdown=True)
80+
81+
# Backup all contents first if option enabled
82+
if backup_first:
83+
log_to_ui("Zipping up your library to backup... (May take a while)")
84+
make_archive(daz_user_library_path, os.path.join(backup_zip_path, "UserLibraryBackup.zip"))
85+
86+
87+
88+
for (root, dirs, files) in user_library:
89+
for dir in dirs:
90+
full_directory = f'{root}/{dir}'
91+
user_dir_no_parent = full_directory.replace(daz_user_library_path,'')
92+
93+
# If the path (without the base) of the user library matches the default library except for case...
94+
user_default_variance = user_dir_no_parent.casefold() in default_lowered_and_parent_dropped \
95+
and user_dir_no_parent not in default_parent_dropped
96+
97+
# Get the path from the default library that corresponds to the user folder we're currently iterating over
98+
default_path_this_dir = root.replace(daz_user_library_path, daz_default_library_path)
99+
default_library_has_root_folder = os.path.isdir(default_path_this_dir)
100+
default_path_has_subfolder = False
101+
102+
dupe_dirs_in_default_folder = list()
103+
# print(default_path_this_dir)
104+
# print(dir)
105+
if default_library_has_root_folder:
106+
dupe_dirs_in_default_folder = [x for x in os.listdir(default_path_this_dir) if dir.casefold() == x.casefold()]
107+
108+
# Get the current subfolder in the case of the default library
109+
# Get the first item in the list - note that with the above check ^^^, we ensure there's only 1
110+
if dupe_dirs_in_default_folder:
111+
default_subfolder_this_dir = dupe_dirs_in_default_folder[0]
112+
default_fullpath_this_dir = os.path.join(default_path_this_dir, default_subfolder_this_dir)
113+
default_path_has_subfolder = os.path.isdir(default_fullpath_this_dir)
114+
115+
# if default_path_has_subfolder:
116+
# print(f"DEFAULT LIBRARY SUBFOLDER: {default_fullpath_this_dir}")
117+
118+
if len(dupe_dirs_in_default_folder) > 1:
119+
log_to_ui('----------------------ERROR-------------------------')
120+
log_to_ui('-----------The DEFAULT DAZ library has multiple folders with the same case!-----------')
121+
log_to_ui('This must be resolved first. Please combine these manually into the desired case')
122+
log_to_ui('If the folder was created by DAZ\'s installer, it is recommended to choose the case of that folder')
123+
log_to_ui('The following folder is the one at issue:')
124+
log_to_ui(default_path_this_dir)
125+
raise HaltException(f"Stopping: DAZ default library has dupclicate folders!\n{default_path_this_dir}/{dir}")
126+
# else:
127+
# log_to_ui("Main Library consistency check passed...")
128+
129+
# Before addressing the mismatch to the default DAZ library, the user's library might well have another same-named, but different-cased,
130+
# folder WITHIN that user library (common cause of the issue to begin with)
131+
# If that is the case, we should detect that and combine all the files into one folder, then remove the other (renaming as appropriate)
132+
dupe_dirs_in_this_folder = [x for x in os.listdir(root) if dir.casefold() == x.casefold()]
133+
# print(f'Dupe dirs length: {len(dupe_dirs_in_this_folder)}')
134+
if len(dupe_dirs_in_this_folder) >= 2:
135+
log_to_ui('\nMultiple folders w/ the same "Name" found in USER library:')
136+
log_to_ui(root)
137+
for dupe in dupe_dirs_in_this_folder:
138+
log_to_ui(dupe)
139+
140+
# if dupe_dirs_in_default_folder:
141+
# If this comes back true, we know there's a folder w/ the same name, but different case when comparing the original DAZ vs. the user's library
142+
if user_default_variance and default_path_has_subfolder:
143+
chosen_subdirectory = dupe_dirs_in_default_folder[0]
144+
chosen_full_path = os.path.join(root, chosen_subdirectory)
145+
else:
146+
log_to_ui('\nThere is no folder with this name in the default DAZ library')
147+
# If there are multiple folders in the user's library, but none in the DAZ default library, we need to pick, but can't use the name in the DAZ default directory
148+
# First, here, create a dictionary that stores the subfolder name & the number of ojbects in it
149+
dupe_subdirectories = dict()
150+
log_to_ui("Fixing user-only library duplicate folder. Consolidating into the folder with the most objects...")
151+
for item in dupe_dirs_in_this_folder:
152+
count = len(os.listdir(os.path.join(root, item)))
153+
dupe_subdirectories[item] = count
154+
155+
# Pick based on the directory with the largest # of items in it
156+
chosen_sub_directory = list(dupe_subdirectories.keys())[list(dupe_subdirectories.values()).index(count)]
157+
chosen_full_path = os.path.join(root, chosen_sub_directory)
158+
159+
move_to_new_home(dupe_dirs_in_this_folder, chosen_full_path, root)
160+
# else:
161+
# log_to_ui("No User Library issues found...")
162+
163+
164+
# If the USER library does not have multiple folders w/ the same name,
165+
# but there remains the difference between the default & user libraries
166+
# In this case, simply rename the directory to the case of the default library
167+
if len(dupe_dirs_in_this_folder) <= 1 and user_default_variance:
168+
log_to_ui(f"\nUser library is unique but does not match the DAZ default library...")
169+
log_to_ui(f'FOLDER: {root}')
170+
171+
old_path = os.path.join(root, dir)
172+
new_path = os.path.join(root, dupe_dirs_in_default_folder[0])
173+
log_to_ui("RENAMING user library:")
174+
log_to_ui(f'OLD: {dir}')
175+
log_to_ui(f'NEW: {dupe_dirs_in_default_folder[0]}')
176+
177+
# Here we just need to rename to the default case
178+
os.rename(old_path, new_path)
179+
180+
globals.process_running = False
181+
log_to_ui("Complete!")
182+
183+
184+
185+
186+
187+
188+
189+
190+

daz_linux_casefix.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
from PyQt6.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox
2+
from PyQt6 import uic
3+
import qdarktheme
4+
5+
import sys
6+
import os
7+
import pickle
8+
9+
from DAZFix.LibraryFix import fix_libraries, log_to_ui
10+
11+
import globals
12+
13+
14+
15+
class DAZWranglerApp(QMainWindow):
16+
def __init__(self):
17+
super().__init__()
18+
self.ui = uic.loadUi(os.path.join(os.getcwd(), 'daz_linux_casefix.ui'), self)
19+
self.show()
20+
21+
globals.ui_object = self
22+
globals.process_running = False
23+
24+
self.load_paths()
25+
26+
self.ui.btnBackupPath.clicked.connect(self.select_backup_path)
27+
self.ui.btnSelectDAZPath.clicked.connect(self.select_daz_path)
28+
self.ui.btnUserPath.clicked.connect(self.select_user_path)
29+
self.ui.btnFixDirectories.clicked.connect(self.fix_directories)
30+
31+
self.ui.btnSavePaths.clicked.connect(self.save_paths)
32+
33+
34+
35+
def save_paths(self):
36+
path_dict = dict()
37+
path_dict['backup_path'] = self.ui.txtBackupPath.text()
38+
path_dict['daz_library'] = self.ui.txtDAZMainPath.text()
39+
path_dict['user_library'] = self.ui.txtUserPath.text()
40+
41+
path = os.path.join(os.getcwd(), "LibraryPaths.pkl")
42+
with open(path, 'wb') as file:
43+
pickle.dump(path_dict, file)
44+
45+
log_to_ui(f"Paths saved to {path}")
46+
47+
48+
49+
def load_paths(self):
50+
path = os.path.join(os.getcwd(), "LibraryPaths.pkl")
51+
if os.path.isfile(path):
52+
with open(path, 'rb') as file:
53+
path_dict = pickle.load(file)
54+
55+
self.ui.txtBackupPath.setText(path_dict['backup_path'])
56+
self.ui.txtDAZMainPath.setText(path_dict['daz_library'])
57+
self.ui.txtUserPath.setText(path_dict['user_library'])
58+
59+
log_to_ui(f"Paths loaded from {path}")
60+
61+
62+
63+
def select_backup_path(self):
64+
try:
65+
folder = QFileDialog.getExistingDirectory(self, "Select Folder")
66+
if len(folder) > 0:
67+
self.ui.txtBackupPath.setText(folder)
68+
except Exception as argument:
69+
msg_box = QMessageBox(self)
70+
msg_box.setWindowTitle("Backup Path Folder Error")
71+
msg_box.setText(f"Error selecting output folder:\n{argument}")
72+
msg_box.show()
73+
return
74+
75+
76+
77+
def select_daz_path(self):
78+
try:
79+
folder = QFileDialog.getExistingDirectory(self, "Select Folder")
80+
if len(folder) > 0:
81+
self.ui.txtDAZMainPath.setText(folder)
82+
except Exception as argument:
83+
msg_box = QMessageBox(self)
84+
msg_box.setWindowTitle("DAZ Path Folder Error")
85+
msg_box.setText(f"Error selecting output folder:\n{argument}")
86+
msg_box.show()
87+
return
88+
89+
90+
def select_user_path(self):
91+
try:
92+
folder = QFileDialog.getExistingDirectory(self, "Select Folder")
93+
if len(folder) > 0:
94+
self.ui.txtUserPath.setText(folder)
95+
except Exception as argument:
96+
msg_box = QMessageBox(self)
97+
msg_box.setWindowTitle("User Path Folder Error")
98+
msg_box.setText(f"Error selecting output folder:\n{argument}")
99+
msg_box.show()
100+
return
101+
102+
103+
def fix_directories(self):
104+
backup = self.ui.chkBackup.isChecked()
105+
backup_path = self.ui.txtBackupPath.text()
106+
main = self.ui.txtDAZMainPath.text()
107+
user = self.ui.txtUserPath.text()
108+
109+
if backup and len(backup_path) == 0:
110+
msg_box = QMessageBox(self)
111+
msg_box.setWindowTitle("Missing Backup Path")
112+
msg_box.setText("Backup is selected, but no backup path is selected!")
113+
msg_box.show()
114+
return
115+
116+
if len(main) == 0:
117+
msg_box = QMessageBox(self)
118+
msg_box.setWindowTitle("Missing User Path")
119+
msg_box.setText('Please enter a path for the main DAZ libary (Typically ending in "My DAZ 3D Library")')
120+
msg_box.show()
121+
return
122+
123+
if len(user) == 0:
124+
msg_box = QMessageBox(self)
125+
msg_box.setWindowTitle("Missing DAZ or User Path")
126+
msg_box.setText('Please enter a path for your user libary')
127+
msg_box.show()
128+
return
129+
130+
# print(f'Backup? {backup}')
131+
# print(f'Backup Path: {backup_path}')
132+
# print(f'Main Path: {main}')
133+
# print(f'User Path: {user}')
134+
135+
try:
136+
fix_libraries(backup, backup_path, main, user)
137+
except Exception as argument:
138+
msg_box = QMessageBox(self)
139+
msg_box.setWindowTitle("ERROR: Fixing Libararies")
140+
msg_box.setText(argument)
141+
msg_box.show()
142+
return
143+
144+
145+
146+
147+
148+
149+
if __name__ == '__main__':
150+
# Prints out the themes available
151+
# print(QStyleFactory.keys())
152+
153+
# user_login_name = os.getlogin()
154+
# print(user_login_name)
155+
156+
app = QApplication(sys.argv)
157+
app.setStyle('Fusion')
158+
app.setStyleSheet(qdarktheme.load_stylesheet())
159+
160+
print(app.style().objectName())
161+
AnachronoxDATUI = DAZWranglerApp()
162+
163+
sys.exit(app.exec())

0 commit comments

Comments
 (0)