-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpatch_maker.py
235 lines (192 loc) · 9.93 KB
/
patch_maker.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QFrame,
QSplitter, QStyleFactory, QApplication, QMessageBox, QLabel,
QComboBox, QLineEdit, QPushButton, QCheckBox, QSlider, QLCDNumber,
QPlainTextEdit, QMenuBar, QMainWindow, QFileDialog, QProgressBar)
from PyQt5.QtCore import Qt, QCoreApplication
from PyQt5.QtGui import QIcon, QTextCursor
import os
import sys
import json
import shutil
import hashlib
class FlattenLog(QMainWindow):
def __init__(self, parent=None):
super(FlattenLog, self).__init__(parent)
self.debug = False
self.init_ui()
def init_ui(self):
self.b = QPlainTextEdit(self)
self.b.setReadOnly(True)
self.b.move(50, 50)
self.b.resize(600, 400)
self.setFixedSize(700, 500)
self.setWindowTitle('Flatten Log')
def write(self, text):
if self.debug:
self.b.insertPlainText(text)
self.b.moveCursor(QTextCursor.End)
class Patcher(QMainWindow):
def __init__(self):
super().__init__()
self.help_text_full = """The Patch Maker contains three major areas, the patch directory selection, the tree.json selection and the flatten button. Once you've filled out everything and you click the "Flatten" button the patch maker will create a "patch" folder next to the folder you've selected and using the tree.json you have provided will find all the files that have been modified since the last patch and copy them to that folder. The tree.json selection is optional but very useful, it will compare the files with the tree.json file you provide to figure out which files have been changed since the last patch so that you only have to upload the change files instead of the entire directory all over again. If you do not have a tree.json do not worry, the patch maker is perfectly capable of generating one on its own, just leave the area blank and the patch maker will generate a new tree.json. Steps to use the patch maker:<ol><li>Select the AOTR directory containing all the files for the next update.</li><li>Optionally, select the tree.json that was generated during the making of the previous patch.</li><li>Click flatten.<ul><li>If you have selected a valid tree.json then the patch maker will move over all the files that have been edited since the time that the selected tree.json was created</li><li>If you have not selected a tree.json, the patch maker will copy over everyfile that in the selected directory.</ul></ol>"""
self.init_ui()
def init_ui(self):
label = QLabel("Select an directory to flatten:", self)
label.move(25, 55)
label.adjustSize()
self.directory = QLineEdit(self)
self.directory.resize(600, 30)
self.directory.move(25, 80)
self.pick_directory_btn = QPushButton("...", self)
self.pick_directory_btn.resize(25, 25)
self.pick_directory_btn.move(635, 85)
self.pick_directory_btn.clicked.connect(self.pick_directory)
label = QLabel("Select an optional tree.json:", self)
label.move(25, 115)
label.adjustSize()
self.tree = QLineEdit(self)
self.tree.resize(600, 30)
self.tree.move(25, 140)
self.pick_tree_btn = QPushButton("...", self)
self.pick_tree_btn.resize(25, 25)
self.pick_tree_btn.move(635, 145)
self.pick_tree_btn.clicked.connect(self.pick_tree)
self.flatten_btn = QPushButton("Flatten", self)
self.flatten_btn.resize(250, 100)
self.flatten_btn.move(225, 200)
self.flatten_btn.clicked.connect(self.flatten_handler)
self.flatten_btn.setEnabled(False)
self.flatten_btn.setToolTip("Select an directory to flatten to unlock the button")
self.check_btn = QPushButton("Check", self)
self.check_btn.resize(250, 100)
self.check_btn.move(225, 325)
self.check_btn.clicked.connect(self.checker)
self.check_btn.setEnabled(False)
self.about_window = QMessageBox()
self.about_window.setIcon(QMessageBox.Information)
self.about_window.setText("About the AOTR Patch Maker")
self.about_window.setInformativeText("The Patch Maker is a tool which \"flattens\" the Age of the Ring directory and prepares it for upload so that the launcher can work out which files to download more easily.")
self.about_window.setStandardButtons(QMessageBox.Ok)
self.about_window.setWindowTitle("About")
self.about_window.buttonClicked.connect(self.about_window.close)
self.help_window = QMessageBox()
self.help_window.setIcon(QMessageBox.Information)
self.help_window.setText("How to use the Patch Maker")
self.help_window.setInformativeText(self.help_text_full)
self.help_window.setStandardButtons(QMessageBox.Ok)
self.help_window.setWindowTitle("About")
self.help_window.buttonClicked.connect(self.help_window.close)
label = QLabel("Version Number", self)
label.move(50, 205)
label.adjustSize()
self.version = QLineEdit(self)
self.version.resize(80, 30)
self.version.move(50, 230)
self.debug = QCheckBox("Debug?", self)
self.debug.move(550, 230)
bar = self.menuBar()
bar.setStyleSheet("QMenuBar {background-color: white;}")
about_act = bar.addAction('About')
about_act.triggered.connect(self.about_window.show)
help_act = bar.addAction('Help')
help_act.triggered.connect(self.help_window.show)
self.log = FlattenLog(self)
self.setFixedSize(700, 450)
self.setWindowTitle('Patch Maker')
self.show()
def flatten_handler(self):
try:
self.setEnabled(False)
self.log.debug = self.debug.isChecked()
reply = QMessageBox.warning(self, "Warning", "This will create a a flattened copy of this directory. Are you sure you wish to continue?", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
if reply == QMessageBox.No:
return
if self.debug.isChecked():
self.log.b.clear()
self.log.show()
self.flatten()
except Exception as e:
QMessageBox.critical(self, "Error", str(e), QMessageBox.Ok, QMessageBox.Ok)
else:
QMessageBox.information(self, "Done", "Finished flattening")
finally:
self.setEnabled(True)
def pick_directory(self):
text = str(QFileDialog.getExistingDirectory(self, f"Select flatten directory"))
if text != "":
self.directory.setText(text)
self.flatten_btn.setEnabled(True)
self.check_btn.setEnabled(True)
def pick_tree(self):
text = str(QFileDialog.getOpenFileName(self, f"Select tree file")[0])
self.tree.setText(text)
if self.version.text() == "" and self.tree.text() != "":
with open(text, "r") as f:
l = json.load(f)
self.version.setText(l["version"])
def hash_file(self, path):
hasher = hashlib.md5()
with open(path, 'rb') as afile:
buf = afile.read()
hasher.update(buf)
return hasher.hexdigest()
def checker(self):
self.log.b.clear()
self.log.show()
self.log.debug = True
for path, _, files in os.walk(self.directory.text()):
for name in files:
QCoreApplication.processEvents()
if name in ["desktop.ini"]:
continue
if "(1)" in name or "(2)" in name or "(3)" in name:
file_path = os.path.join(path, name)
subdir_path = file_path.replace(f"{self.directory.text()}\\", '')
self.log.write(f"Duplicate file {subdir_path}\n")
self.log.write("Done.")
self.debug.isChecked()
def flatten(self):
QCoreApplication.processEvents()
try:
with open(self.tree.text(), "r") as f:
old_tree = json.load(f)
except FileNotFoundError:
self.log.write("tree.json not specified, generating blank one\n")
old_tree = {}
tree = {"files": {}, "version": self.version.text() or old_tree["version"]}
source_dir = os.path.join(self.directory.text(), '..', 'AgeoftheRingUpdate')
new_dir = os.path.join(source_dir, "source")
try:
self.log.write("Creating release directory one level up\n")
os.mkdirs(new_dir)
except FileExistsError as e:
QMessageBox.critical(self, "Dir Error", str(e), QMessageBox.Ok, QMessageBox.Ok)
return
for path, _, files in os.walk(self.directory.text()):
for name in files:
QCoreApplication.processEvents()
if name in ["desktop.ini"]:
continue
file_path = os.path.join(path, name)
subdir_path = file_path.replace(f"{self.directory.text()}\\", '')
md5 = self.hash_file(file_path)
try:
old_md5 = old_tree["files"][subdir_path]["hash"]
except KeyError:
old_md5 = ""
tree["files"][subdir_path] = {"name": name, "path": subdir_path, "hash": md5}
self.log.write(f"{file_path}: Old '{old_md5}' vs '{md5}' New\n")
if md5 != old_md5:
try:
new_path = os.path.join(new_dir, subdir_path.replace("\\", ".").lower())
shutil.copy(file_path, new_path)
self.log.write(f"Successfully moved {file_path} to {new_path}\n")
except shutil.Error:
QMessageBox.critical(self, "shutil Error", f"{str(e)}\n{name}")
with open(os.path.join(source_dir, "tree.json"), "w+") as f:
json.dump(tree, f, indent=4)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('Fusion')
gui = Patcher()
sys.exit(app.exec_())