Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #4184 from pypeclub/feature/OP-4566_File-transacti…
Browse files Browse the repository at this point in the history
…ons-Handle-cases-when-source-and-destination-is-the-same

File transactions: Source path is destination path
  • Loading branch information
iLLiCiTiT authored Dec 6, 2022
2 parents 272fd1e + 2c55ee5 commit 2e17bf4
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 40 deletions.
87 changes: 50 additions & 37 deletions openpype/lib/file_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@


class FileTransaction(object):
"""
"""File transaction with rollback options.
The file transaction is a three step process.
The file transaction is a three-step process.
1) Rename any existing files to a "temporary backup" during `process()`
2) Copy the files to final destination during `process()`
Expand All @@ -39,14 +39,12 @@ class FileTransaction(object):
Warning:
Any folders created during the transfer will not be removed.
"""

MODE_COPY = 0
MODE_HARDLINK = 1

def __init__(self, log=None):

if log is None:
log = logging.getLogger("FileTransaction")

Expand All @@ -63,49 +61,64 @@ def __init__(self, log=None):
self._backup_to_original = {}

def add(self, src, dst, mode=MODE_COPY):
"""Add a new file to transfer queue"""
"""Add a new file to transfer queue.
Args:
src (str): Source path.
dst (str): Destination path.
mode (MODE_COPY, MODE_HARDLINK): Transfer mode.
"""

opts = {"mode": mode}

src = os.path.abspath(src)
dst = os.path.abspath(dst)
src = os.path.normpath(os.path.abspath(src))
dst = os.path.normpath(os.path.abspath(dst))

if dst in self._transfers:
queued_src = self._transfers[dst][0]
if src == queued_src:
self.log.debug("File transfer was already "
"in queue: {} -> {}".format(src, dst))
self.log.debug(
"File transfer was already in queue: {} -> {}".format(
src, dst))
return
else:
self.log.warning("File transfer in queue replaced..")
self.log.debug("Removed from queue: "
"{} -> {}".format(queued_src, dst))
self.log.debug("Added to queue: {} -> {}".format(src, dst))
self.log.debug(
"Removed from queue: {} -> {} replaced by {} -> {}".format(
queued_src, dst, src, dst))

self._transfers[dst] = (src, opts)

def process(self):

# Backup any existing files
for dst in self._transfers.keys():
if os.path.exists(dst):
# Backup original file
# todo: add timestamp or uuid to ensure unique
backup = dst + ".bak"
self._backup_to_original[backup] = dst
self.log.debug("Backup existing file: "
"{} -> {}".format(dst, backup))
os.rename(dst, backup)
for dst, (src, _) in self._transfers.items():
if dst == src or not os.path.exists(dst):
continue

# Backup original file
# todo: add timestamp or uuid to ensure unique
backup = dst + ".bak"
self._backup_to_original[backup] = dst
self.log.debug(
"Backup existing file: {} -> {}".format(dst, backup))
os.rename(dst, backup)

# Copy the files to transfer
for dst, (src, opts) in self._transfers.items():
if dst == src:
self.log.debug(
"Source and destionation are same files {} -> {}".format(
src, dst))
continue

self._create_folder_for_file(dst)

if opts["mode"] == self.MODE_COPY:
self.log.debug("Copying file ... {} -> {}".format(src, dst))
copyfile(src, dst)
elif opts["mode"] == self.MODE_HARDLINK:
self.log.debug("Hardlinking file ... {} -> {}".format(src,
dst))
self.log.debug("Hardlinking file ... {} -> {}".format(
src, dst))
create_hard_link(src, dst)

self._transferred.append(dst)
Expand All @@ -116,37 +129,37 @@ def finalize(self):
try:
os.remove(backup)
except OSError:
self.log.error("Failed to remove backup file: "
"{}".format(backup),
exc_info=True)
self.log.error(
"Failed to remove backup file: {}".format(backup),
exc_info=True)

def rollback(self):

errors = 0

# Rollback any transferred files
for path in self._transferred:
try:
os.remove(path)
except OSError:
errors += 1
self.log.error("Failed to rollback created file: "
"{}".format(path),
exc_info=True)
self.log.error(
"Failed to rollback created file: {}".format(path),
exc_info=True)

# Rollback the backups
for backup, original in self._backup_to_original.items():
try:
os.rename(backup, original)
except OSError:
errors += 1
self.log.error("Failed to restore original file: "
"{} -> {}".format(backup, original),
exc_info=True)
self.log.error(
"Failed to restore original file: {} -> {}".format(
backup, original),
exc_info=True)

if errors:
self.log.error("{} errors occurred during "
"rollback.".format(errors), exc_info=True)
self.log.error(
"{} errors occurred during rollback.".format(errors),
exc_info=True)
six.reraise(*sys.exc_info())

@property
Expand Down
3 changes: 0 additions & 3 deletions openpype/plugins/publish/integrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,6 @@ def register(self, instance, file_transactions, filtered_repres):
instance)

for src, dst in prepared["transfers"]:
if src == dst:
continue

# todo: add support for hardlink transfers
file_transactions.add(src, dst)

Expand Down

0 comments on commit 2e17bf4

Please sign in to comment.