Skip to content

Commit 9d90012

Browse files
authored
Move files from temporary directory changer (#2022)
1 parent 33acc81 commit 9d90012

File tree

5 files changed

+121
-13
lines changed

5 files changed

+121
-13
lines changed

armi/bookkeeping/db/database.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
from armi.reactor.reactors import Core, Reactor
7979
from armi.settings.fwSettings.globalSettings import CONF_SORT_REACTOR
8080
from armi.utils import getNodesPerCycle
81+
from armi.utils import safeMove
8182
from armi.utils.textProcessors import resolveMarkupInclusions
8283

8384
# CONSTANTS
@@ -312,7 +313,7 @@ def close(self, completedSuccessfully=False):
312313

313314
if self._permission == "w":
314315
# move out of the FAST_PATH and into the working directory
315-
newPath = shutil.move(self._fullPath, self._fileName)
316+
newPath = safeMove(self._fullPath, self._fileName)
316317
self._fullPath = os.path.abspath(newPath)
317318

318319
def splitDatabase(
@@ -351,7 +352,7 @@ def splitDatabase(
351352
backupDBPath = os.path.abspath(label.join(os.path.splitext(self._fileName)))
352353
runLog.info("Retaining full database history in {}".format(backupDBPath))
353354
if self._fullPath is not None:
354-
shutil.move(self._fullPath, backupDBPath)
355+
safeMove(self._fullPath, backupDBPath)
355356

356357
self.h5db = h5py.File(self._fullPath, self._permission)
357358
dbOut = self.h5db

armi/utils/__init__.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,50 @@ def safeCopy(src: str, dst: str) -> None:
840840
runLog.extra("Copied {} -> {}".format(src, dst))
841841

842842

843+
def safeMove(src: str, dst: str) -> None:
844+
"""Check that a file has been successfully moved before continuing."""
845+
# Convert files to OS-independence
846+
src = os.path.abspath(src)
847+
dst = os.path.abspath(dst)
848+
if os.path.isdir(dst):
849+
dst = os.path.join(dst, os.path.basename(src))
850+
851+
srcSize = os.path.getsize(src)
852+
if "win" in sys.platform:
853+
# this covers Windows ("win32") and MacOS ("darwin")
854+
shutil.move(src, dst)
855+
elif "linux" in sys.platform:
856+
cmd = f'mv "{src}" "{dst}"'
857+
os.system(cmd)
858+
else:
859+
raise OSError(
860+
"Cannot perform ``safeMove`` on files because ARMI only supports "
861+
+ "Linux, MacOS, and Windows."
862+
)
863+
864+
waitTime = 0.01 # 10 ms
865+
maxWaitTime = 6000 # 1 min
866+
totalWaitTime = 0
867+
while True:
868+
try:
869+
dstSize = os.path.getsize(dst)
870+
if srcSize == dstSize:
871+
break
872+
except FileNotFoundError:
873+
pass
874+
time.sleep(waitTime)
875+
totalWaitTime += waitTime
876+
if totalWaitTime > maxWaitTime:
877+
runLog.warning(
878+
f"File move from {dst} to {src} has failed due to exceeding "
879+
+ f"a maximum wait time of {maxWaitTime/60} minutes."
880+
)
881+
return
882+
883+
runLog.extra("Moved {} -> {}".format(src, dst))
884+
return dst
885+
886+
843887
# Allow us to check the copy operation is complete before continuing
844888
shutil_copy = shutil.copy
845889
shutil.copy = safeCopy

armi/utils/directoryChangers.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from armi import context
2323
from armi import runLog
2424
from armi.utils import pathTools
25+
from armi.utils import safeMove
2526

2627

2728
def _changeDirectory(destination):
@@ -128,19 +129,27 @@ def moveFiles(self):
128129
"""Copy ``filesToMove`` into the destination directory on entry."""
129130
initialPath = self.initial
130131
destinationPath = self.destination
131-
self._transferFiles(initialPath, destinationPath, self._filesToMove)
132+
self._transferFiles(
133+
initialPath, destinationPath, self._filesToMove, moveFiles=False
134+
)
132135
if self.outputPath != self.initial:
133136
destinationPath = self.outputPath
134-
self._transferFiles(initialPath, destinationPath, self._filesToMove)
137+
self._transferFiles(
138+
initialPath, destinationPath, self._filesToMove, moveFiles=False
139+
)
135140

136141
def retrieveFiles(self):
137142
"""Copy ``filesToRetrieve`` back into the initial directory on exit."""
138-
initialPath = self.destination
139-
destinationPath = self.initial
140-
self._transferFiles(initialPath, destinationPath, self._filesToRetrieve)
141143
if self.outputPath != self.initial:
142-
destinationPath = self.outputPath
143-
self._transferFiles(initialPath, destinationPath, self._filesToRetrieve)
144+
self._transferFiles(
145+
self.destination,
146+
self.outputPath,
147+
self._filesToRetrieve,
148+
moveFiles=False,
149+
)
150+
self._transferFiles(
151+
self.destination, self.initial, self._filesToRetrieve, moveFiles=True
152+
)
144153

145154
def _retrieveEntireFolder(self):
146155
"""
@@ -173,7 +182,7 @@ def _createOutputDirectory(self):
173182
runLog.extra(f"Output folder already exists: {self.outputPath}")
174183

175184
@staticmethod
176-
def _transferFiles(initialPath, destinationPath, fileList):
185+
def _transferFiles(initialPath, destinationPath, fileList, moveFiles=False):
177186
"""
178187
Transfer files into or out of the directory.
179188
@@ -192,6 +201,8 @@ def _transferFiles(initialPath, destinationPath, fileList):
192201
files will be transferred. Alternatively tuples of (initialName, finalName) are allowed
193202
if you want the file renamed during transit. In the non-tuple option, globs/wildcards
194203
are allowed.
204+
moveFiles: bool, optional
205+
Controls whether the files are "moved" (``mv``) or "copied" (``cp``)
195206
196207
Warning
197208
-------
@@ -223,8 +234,12 @@ def _transferFiles(initialPath, destinationPath, fileList):
223234
continue
224235

225236
toPath = os.path.join(destinationPath, destName)
226-
runLog.extra("Copying {} to {}".format(fromPath, toPath))
227-
shutil.copy(fromPath, toPath)
237+
if moveFiles:
238+
runLog.extra("Moving {} to {}".format(fromPath, toPath))
239+
safeMove(fromPath, toPath)
240+
else:
241+
runLog.extra("Copying {} to {}".format(fromPath, toPath))
242+
shutil.copy(fromPath, toPath)
228243

229244

230245
class TemporaryDirectoryChanger(DirectoryChanger):

armi/utils/tests/test_utils.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
getStepLengths,
4242
hasBurnup,
4343
safeCopy,
44+
safeMove,
4445
)
4546

4647

@@ -218,6 +219,53 @@ def test_safeCopy(self):
218219
self.assertIn("Copied", mock.getStdout())
219220
self.assertIn("file2", mock.getStdout())
220221
self.assertIn("->", mock.getStdout())
222+
self.assertTrue(os.path.exists(os.path.join("dir2", "file1.txt")))
223+
224+
def test_safeMove(self):
225+
with directoryChangers.TemporaryDirectoryChanger():
226+
os.mkdir("dir1")
227+
os.mkdir("dir2")
228+
file1 = "dir1/file1.txt"
229+
with open(file1, "w") as f:
230+
f.write("Hello")
231+
file2 = "dir1\\file2.txt"
232+
with open(file2, "w") as f:
233+
f.write("Hello2")
234+
235+
with mockRunLogs.BufferLog() as mock:
236+
# Test Linuxy file path
237+
self.assertEqual("", mock.getStdout())
238+
safeMove(file1, "dir2")
239+
self.assertIn("Moved", mock.getStdout())
240+
self.assertIn("file1", mock.getStdout())
241+
self.assertIn("->", mock.getStdout())
242+
# Clean up for next safeCopy
243+
mock.emptyStdout()
244+
# Test Windowsy file path
245+
self.assertEqual("", mock.getStdout())
246+
safeMove(file2, "dir2")
247+
self.assertIn("Moved", mock.getStdout())
248+
self.assertIn("file2", mock.getStdout())
249+
self.assertIn("->", mock.getStdout())
250+
self.assertTrue(os.path.exists(os.path.join("dir2", "file1.txt")))
251+
252+
def test_safeMoveDir(self):
253+
with directoryChangers.TemporaryDirectoryChanger():
254+
os.mkdir("dir1")
255+
file1 = "dir1/file1.txt"
256+
with open(file1, "w") as f:
257+
f.write("Hello")
258+
file2 = "dir1\\file2.txt"
259+
with open(file2, "w") as f:
260+
f.write("Hello2")
261+
262+
with mockRunLogs.BufferLog() as mock:
263+
self.assertEqual("", mock.getStdout())
264+
safeMove("dir1", "dir2")
265+
self.assertIn("Moved", mock.getStdout())
266+
self.assertIn("dir1", mock.getStdout())
267+
self.assertIn("dir2", mock.getStdout())
268+
self.assertTrue(os.path.exists(os.path.join("dir2", "file1.txt")))
221269

222270

223271
class CyclesSettingsTests(unittest.TestCase):

doc/release/0.5.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Release Date: TBD
88

99
New Features
1010
------------
11-
#. TBD
11+
#. Move instead of copy files from TemporaryDirectoryChanger. (`PR#2022 <https://github.com/terrapower/armi/pull/2022>`_)
1212

1313
API Changes
1414
-----------

0 commit comments

Comments
 (0)