Skip to content

Commit 52c0b5b

Browse files
authored
Merge pull request #30 from chrisd149/auto-saver-feature
feature: file autosaver
2 parents 2527f84 + 607ba56 commit 52c0b5b

3 files changed

Lines changed: 175 additions & 0 deletions

File tree

toontown/leveleditor/AutoSaver.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import os
2+
import shutil
3+
import time
4+
import threading
5+
6+
from .DNASerializer import DNASerializer
7+
8+
9+
class AutoSaver:
10+
autoSaverToggled = False
11+
autoSaverInterval = 15.0
12+
maxAutoSaveCount = 10.0
13+
14+
@staticmethod
15+
def initializeAutoSaver():
16+
threading.Thread(target = AutoSaver.autoSaverProcess, daemon = True).start()
17+
# Creates 'autosaves' directory in user data directory
18+
if not os.path.isdir('leveleditor/autosaves'):
19+
os.mkdir('leveleditor/autosaves')
20+
print('Created "autosaves" dir')
21+
22+
@staticmethod
23+
def autoSaverProcess():
24+
while True:
25+
autoSaverInterval = AutoSaver.autoSaverInterval * 60 # Converts global autoSaverInterval to minutes
26+
# Loops without doing anything if auto saver isn't toggled
27+
if AutoSaver.autoSaverToggled is False:
28+
time.sleep(0.1)
29+
30+
while AutoSaver.autoSaverToggled is True:
31+
# outputFile filename is empty, which may occur if filename is left blank in file prompt
32+
if DNASerializer.outputFile is None:
33+
print('No file loaded, exiting auto saving loop...')
34+
DNASerializer.autoSaveCount = 0
35+
DNASerializer.autoSaverMgrRunning = False
36+
AutoSaver.autoSaverToggled = False
37+
break
38+
39+
# Epoch time of next auto save
40+
endTime = time.time() + autoSaverInterval
41+
# Loops until endTime is reached or the auto saver is un-toggled by user
42+
while time.time() <= endTime and AutoSaver.autoSaverToggled is True:
43+
time.sleep(0.1)
44+
# Only auto save if auto save is toggled
45+
if AutoSaver.autoSaverToggled is True:
46+
AutoSaver.manageAutoSaveFiles()
47+
# Sets autoSaveCount
48+
if DNASerializer.autoSaveCount >= AutoSaver.maxAutoSaveCount:
49+
DNASerializer.autoSaveCount = AutoSaver.maxAutoSaveCount
50+
else:
51+
DNASerializer.autoSaveCount += 1
52+
53+
@staticmethod
54+
def manageAutoSaveFiles():
55+
DNASerializer.autoSaverMgrRunning = True
56+
autoSaveCount = int(DNASerializer.autoSaveCount)
57+
58+
# Defining outputFile name properties
59+
base = os.path.basename(DNASerializer.outputFile)
60+
dir = os.path.dirname(DNASerializer.outputFile)
61+
basename, extension = os.path.splitext(base)
62+
63+
# Renames output file to auto save file naming convention
64+
if autoSaveCount == 0:
65+
# Only save & manage 'latest' file
66+
if AutoSaver.maxAutoSaveCount == 0:
67+
# Only save auto save latest file
68+
if basename[-16:] == '_autosave-latest':
69+
DNASerializer.outputDNADefaultFile() # Saves working DNA file
70+
return
71+
DNASerializer.outputFile = os.path.join(dir, 'autosaves', basename + '_autosave-latest' + extension)
72+
# Replaces any back slashes in outputFile with forward slashes
73+
DNASerializer.outputFile = DNASerializer.outputFile.replace('\\', '/')
74+
# Creates new 'autosaves' directory if none is found
75+
# This also accounts for if the directory of outputFile is not in the default user data folder
76+
if not os.path.isdir(os.path.join(dir, 'autosaves')):
77+
os.mkdir(os.path.join(dir, 'autosaves'))
78+
79+
# Deletes 'latest' from filename
80+
basename = basename[:-6]
81+
82+
# Copies auto save files
83+
while autoSaveCount != 0:
84+
filename = os.path.join(dir, basename + str(autoSaveCount) + extension)
85+
oldFilename = os.path.join(dir, basename + str(autoSaveCount - 1) + extension)
86+
# Copies working auto save file
87+
if autoSaveCount == 1:
88+
shutil.copy2(DNASerializer.outputFile, filename)
89+
# Incrementally copies each auto save file (i.e. 3400_autosave-1.dna -> 3400_autosave-2.dna)
90+
if autoSaveCount > 1:
91+
shutil.copy2(oldFilename, filename)
92+
autoSaveCount -= 1
93+
94+
DNASerializer.outputDNADefaultFile() # Saves working DNA file
95+
DNASerializer.autoSaverMgrRunning = False

toontown/leveleditor/DNASerializer.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
class DNASerializer:
1111
notify = DirectNotifyGlobal.directNotify.newCategory('LevelEditor')
1212
outputFile = None
13+
# Local AutoSaver variables
14+
autoSaverMgrRunning = False
15+
autoSaveCount = 0
1316

1417
# STYLE/DNA FILE FUNCTIONS
1518
@staticmethod
@@ -24,6 +27,11 @@ def loadSpecifiedDNAFile():
2427
initialdir = path,
2528
title = 'Load DNA File',
2629
parent = base.le.panel.component('hull'))
30+
DNASerializer.autoSaveCount = 0
31+
# Wait until auto saver is done managing files before loading new file
32+
while DNASerializer.autoSaverMgrRunning is True:
33+
if DNASerializer.autoSaverMgrRunning is False:
34+
break
2735
if dnaFilename:
2836
DNASerializer.loadDNAFromFile(dnaFilename)
2937
DNASerializer.outputFile = dnaFilename
@@ -42,6 +50,11 @@ def saveToSpecifiedDNAFile():
4250
initialdir = path,
4351
title = 'Save DNA File as',
4452
parent = base.le.panel.component('hull'))
53+
DNASerializer.autoSaveCount = 0
54+
# Wait until auto saver is done managing files before saving new file
55+
while DNASerializer.autoSaverMgrRunning is True:
56+
if DNASerializer.autoSaverMgrRunning is False:
57+
break
4558
if dnaFilename:
4659
DNASerializer.outputDNA(dnaFilename)
4760
DNASerializer.outputFile = dnaFilename

toontown/leveleditor/LevelEditorPanel.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from direct.tkwidgets import Floater
99

1010
from .DNASerializer import DNASerializer
11+
from .AutoSaver import AutoSaver
1112
from .LevelStyleManager import *
1213
from .LevelEditorGlobals import *
1314
from .LESceneGraphExplorer import *
@@ -127,6 +128,16 @@ def __init__(self, levelEditor, parent = None, **kw):
127128
'Open Injector',
128129
label = 'Injector',
129130
command = self.showInjector)
131+
menuBar.addmenuitem('Advanced', 'separator')
132+
menuBar.addmenuitem('Advanced', 'checkbutton',
133+
'Toggle Auto-saver On/Off',
134+
label = 'Toggle Auto Saver',
135+
command = self.toggleAutoSaver)
136+
menuBar.addmenuitem('Advanced', 'command',
137+
'User Set Auto Saver Options',
138+
label = 'Auto Saver Options',
139+
command = self.showAutoSaverDialog)
140+
130141
# Corporate Clash Old Toontown-esque Filter
131142
if base.server == TOONTOWN_CORPORATE_CLASH:
132143
self.toggleOTVar = IntVar()
@@ -176,6 +187,32 @@ def __init__(self, levelEditor, parent = None, **kw):
176187
message_text = CONTROLS)
177188
self.controlsDialog.withdraw()
178189

190+
self.autoSaverDialog = Pmw.Dialog(parent, title = 'Autosaver Options',
191+
buttons = ('Save Options',),
192+
command = self.setAutoSaverInterval)
193+
self.autoSaverDialog.withdraw()
194+
195+
self.autoSaverDialogInterval = Pmw.Counter(self.autoSaverDialog.interior(),
196+
labelpos = 'w',
197+
label_text = 'Auto save interval in minutes:',
198+
entry_width = 10,
199+
entryfield_value = int(AutoSaver.autoSaverInterval),
200+
entryfield_validate = {'validator': 'real',
201+
'min': 1, 'max': 60})
202+
203+
self.autoSaverDialogMax = Pmw.Counter(self.autoSaverDialog.interior(),
204+
labelpos = 'w',
205+
label_text = 'Max auto save files:',
206+
entry_width = 10,
207+
entryfield_value = int(AutoSaver.maxAutoSaveCount),
208+
entryfield_validate = {'validator': 'numeric',
209+
'min': 0, 'max': 99})
210+
211+
counters = (self.autoSaverDialogInterval, self.autoSaverDialogMax)
212+
Pmw.alignlabels(counters)
213+
for counter in counters:
214+
counter.pack(fill = 'both', expand = 1)
215+
179216
self.editMenu = Pmw.ComboBox(
180217
menuFrame, labelpos = W,
181218
label_text = 'Edit Mode:', entry_width = 18,
@@ -962,6 +999,9 @@ def toggleWidgetHandles(s = self):
962999
# Make sure input variables processed
9631000
self.initialiseoptions(LevelEditorPanel)
9641001

1002+
# Initializes auto saver for use
1003+
AutoSaver.initializeAutoSaver()
1004+
9651005
def updateInfo(self, page):
9661006
if page == 'Signs':
9671007
self.updateSignPage()
@@ -1466,6 +1506,16 @@ def setSuitPointType(self, name):
14661506
def setBattleCellType(self, name):
14671507
self.levelEditor.currentBattleCellType = name
14681508

1509+
def setAutoSaverInterval(self, i):
1510+
if i == 'Save Options':
1511+
try:
1512+
AutoSaver.autoSaverInterval = float(self.autoSaverDialogInterval.get())
1513+
AutoSaver.maxAutoSaveCount = float(self.autoSaverDialogMax.get())
1514+
except ValueError as e:
1515+
# Non-float was passed
1516+
raise e
1517+
self.autoSaverDialog.withdraw()
1518+
14691519
def updateSelectedObjColor(self, color):
14701520
try:
14711521
obj = self.levelEditor.DNATarget
@@ -1506,6 +1556,10 @@ def showControls(self):
15061556
self.controlsDialog.show()
15071557
self.controlsDialog.focus_set()
15081558

1559+
def showAutoSaverDialog(self):
1560+
self.autoSaverDialog.show()
1561+
self.autoSaverDialog.focus_set()
1562+
15091563
def showInjector(self):
15101564
self.injectorDialog.show()
15111565
self.injectorDialog.focus_set()
@@ -1531,3 +1585,16 @@ def toggleDrive(self):
15311585
self.levelEditor.useDriveMode()
15321586
else:
15331587
self.levelEditor.useDirectFly()
1588+
1589+
def toggleAutoSaver(self):
1590+
if AutoSaver.autoSaverToggled is False:
1591+
# If no working DNA outputFile is selected, one is chosen here
1592+
if DNASerializer.outputFile is None:
1593+
DNASerializer.saveToSpecifiedDNAFile()
1594+
print(f'Starting auto saver on an interval of {AutoSaver.autoSaverInterval} minutes')
1595+
# Toggles auto saver to begin auto saving loop
1596+
AutoSaver.autoSaverToggled = True
1597+
else:
1598+
print('Stopping auto saver...')
1599+
# Stops auto saving loop
1600+
AutoSaver.autoSaverToggled = False

0 commit comments

Comments
 (0)