feat(rpgmaker): add scoped image patch preparation
- Patch highlighted editable images or all images when none are highlighted - Skip unchanged images while preserving editable workspace copies - Open the editable folder matching the current image or folder filter
This commit is contained in:
parent
3d82203496
commit
d82895cc4d
4 changed files with 176 additions and 22 deletions
|
|
@ -43,6 +43,7 @@ from util.rpgmaker_images import (
|
||||||
preview_png_bytes,
|
preview_png_bytes,
|
||||||
read_encryption_key,
|
read_encryption_key,
|
||||||
remove_editable_assets,
|
remove_editable_assets,
|
||||||
|
resolve_content_root,
|
||||||
scan_image_assets,
|
scan_image_assets,
|
||||||
thumbnail_png_bytes,
|
thumbnail_png_bytes,
|
||||||
)
|
)
|
||||||
|
|
@ -198,7 +199,8 @@ class RPGMakerImageManager(QWidget):
|
||||||
root.addWidget(title)
|
root.addWidget(title)
|
||||||
intro = QLabel(
|
intro = QLabel(
|
||||||
"Select images while browsing, decrypt them into .dazedtl/images, edit the PNGs, "
|
"Select images while browsing, decrypt them into .dazedtl/images, edit the PNGs, "
|
||||||
"then encrypt the editable images when they are ready. "
|
"then highlight the finished images to patch only those, or clear highlights to "
|
||||||
|
"patch every editable image. "
|
||||||
"Pages keep very large games responsive."
|
"Pages keep very large games responsive."
|
||||||
)
|
)
|
||||||
intro.setWordWrap(True)
|
intro.setWordWrap(True)
|
||||||
|
|
@ -291,7 +293,8 @@ class RPGMakerImageManager(QWidget):
|
||||||
action_row.setSpacing(8)
|
action_row.setSpacing(8)
|
||||||
self.open_workspace_button = QPushButton("Open folder")
|
self.open_workspace_button = QPushButton("Open folder")
|
||||||
self.open_workspace_button.setToolTip(
|
self.open_workspace_button.setToolTip(
|
||||||
"Open the game-local .dazedtl/images folder in your file manager."
|
"Open the highlighted image's editable folder, the chosen folder filter, or the "
|
||||||
|
"editable img root."
|
||||||
)
|
)
|
||||||
self.open_workspace_button.clicked.connect(self._open_editable_folder)
|
self.open_workspace_button.clicked.connect(self._open_editable_folder)
|
||||||
self.decrypt_selected_button = QPushButton("Decrypt highlighted")
|
self.decrypt_selected_button = QPushButton("Decrypt highlighted")
|
||||||
|
|
@ -304,14 +307,14 @@ class RPGMakerImageManager(QWidget):
|
||||||
"untouched and can be decrypted again. The Delete key does the same thing."
|
"untouched and can be decrypted again. The Delete key does the same thing."
|
||||||
)
|
)
|
||||||
self.remove_button.clicked.connect(self._remove_highlighted)
|
self.remove_button.clicked.connect(self._remove_highlighted)
|
||||||
self.prepare_button = QPushButton("Encrypt + patch")
|
self.prepare_button = QPushButton("Encrypt all + patch")
|
||||||
self.prepare_button.setStyleSheet(
|
self.prepare_button.setStyleSheet(
|
||||||
"QPushButton{border:1px solid #4ec9b0;color:#4ec9b0;font-weight:bold;padding:6px 14px;}"
|
"QPushButton{border:1px solid #4ec9b0;color:#4ec9b0;font-weight:bold;padding:6px 14px;}"
|
||||||
"QPushButton:hover{background:#18352f;}"
|
"QPushButton:hover{background:#18352f;}"
|
||||||
)
|
)
|
||||||
self.prepare_button.setToolTip(
|
self.prepare_button.setToolTip(
|
||||||
"Re-encrypt every PNG in the editable folder, preserve original encrypted files in "
|
"With highlighted images, patch only their editable PNGs. With no highlights, patch "
|
||||||
".dazedtl/image_backups, and add exact .gitignore allow-rules."
|
"every editable PNG. Editable copies remain in .dazedtl/images until removed."
|
||||||
)
|
)
|
||||||
self.prepare_button.clicked.connect(self._prepare_checked)
|
self.prepare_button.clicked.connect(self._prepare_checked)
|
||||||
action_buttons = (
|
action_buttons = (
|
||||||
|
|
@ -445,6 +448,7 @@ class RPGMakerImageManager(QWidget):
|
||||||
f"Found {len(assets):,} images · {encrypted:,} encrypted · "
|
f"Found {len(assets):,} images · {encrypted:,} encrypted · "
|
||||||
f"{editable:,} editable PNG copies · {len(self.selected_ids):,} highlighted"
|
f"{editable:,} editable PNG copies · {len(self.selected_ids):,} highlighted"
|
||||||
)
|
)
|
||||||
|
self._update_prepare_scope()
|
||||||
self._apply_filters()
|
self._apply_filters()
|
||||||
|
|
||||||
def _scan_error(self, generation: int, message: str) -> None:
|
def _scan_error(self, generation: int, message: str) -> None:
|
||||||
|
|
@ -549,12 +553,19 @@ class RPGMakerImageManager(QWidget):
|
||||||
self._update_selection_status()
|
self._update_selection_status()
|
||||||
|
|
||||||
def _update_selection_status(self) -> None:
|
def _update_selection_status(self) -> None:
|
||||||
|
self._update_prepare_scope()
|
||||||
self.status_label.setText(
|
self.status_label.setText(
|
||||||
f"{len(self.filtered_assets):,} matching images · "
|
f"{len(self.filtered_assets):,} matching images · "
|
||||||
f"{len(self.selected_ids):,} highlighted · "
|
f"{len(self.selected_ids):,} highlighted · "
|
||||||
f"{sum(asset.has_plain for asset in self.assets):,} editable"
|
f"{sum(asset.has_plain for asset in self.assets):,} editable"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _update_prepare_scope(self) -> None:
|
||||||
|
if self.selected_ids:
|
||||||
|
self.prepare_button.setText("Encrypt highlighted + patch")
|
||||||
|
else:
|
||||||
|
self.prepare_button.setText("Encrypt all + patch")
|
||||||
|
|
||||||
def _show_preview(self, current: QListWidgetItem | None, _previous=None) -> None:
|
def _show_preview(self, current: QListWidgetItem | None, _previous=None) -> None:
|
||||||
if current is None:
|
if current is None:
|
||||||
return
|
return
|
||||||
|
|
@ -595,9 +606,27 @@ class RPGMakerImageManager(QWidget):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
workspace = ensure_editable_workspace(self.game_root)
|
workspace = ensure_editable_workspace(self.game_root)
|
||||||
if not QDesktopServices.openUrl(QUrl.fromLocalFile(str(workspace))):
|
current = self.image_list.currentItem()
|
||||||
|
asset = (
|
||||||
|
self.assets_by_id.get(current.data(_ASSET_ID_ROLE))
|
||||||
|
if current is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
if asset is not None:
|
||||||
|
target = asset.plain_path.parent
|
||||||
|
else:
|
||||||
|
root = Path(self.game_root).expanduser().resolve()
|
||||||
|
content_relative = resolve_content_root(root).relative_to(root)
|
||||||
|
folder = self.folder_combo.currentData() or "img"
|
||||||
|
relative_folder = Path(folder)
|
||||||
|
if relative_folder.is_absolute() or ".." in relative_folder.parts:
|
||||||
|
raise ValueError(f"Invalid editable image folder: {folder}")
|
||||||
|
target = workspace / content_relative / relative_folder
|
||||||
|
target.resolve().relative_to(workspace.resolve())
|
||||||
|
target.mkdir(parents=True, exist_ok=True)
|
||||||
|
if not QDesktopServices.openUrl(QUrl.fromLocalFile(str(target))):
|
||||||
raise RuntimeError("The system file manager could not open the folder.")
|
raise RuntimeError("The system file manager could not open the folder.")
|
||||||
self.status_label.setText(f"Editable images: {workspace}")
|
self.status_label.setText(f"Editable images: {target}")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
QMessageBox.warning(self, "Editable Image Folder", str(exc))
|
QMessageBox.warning(self, "Editable Image Folder", str(exc))
|
||||||
|
|
||||||
|
|
@ -618,12 +647,16 @@ class RPGMakerImageManager(QWidget):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _decrypt_checked(self) -> None:
|
def _decrypt_checked(self) -> None:
|
||||||
assets = [asset for asset in self._selected_assets() if asset.has_encrypted]
|
assets = [
|
||||||
|
asset
|
||||||
|
for asset in self._selected_assets()
|
||||||
|
if asset.has_encrypted and not asset.has_plain
|
||||||
|
]
|
||||||
if not assets:
|
if not assets:
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
"Nothing Selected",
|
"Nothing to Decrypt",
|
||||||
"Select one or more encrypted images first.",
|
"Highlight one or more encrypted images that are not already editable.",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if self._ensure_key():
|
if self._ensure_key():
|
||||||
|
|
@ -667,24 +700,41 @@ class RPGMakerImageManager(QWidget):
|
||||||
self._start_action("remove", assets)
|
self._start_action("remove", assets)
|
||||||
|
|
||||||
def _prepare_checked(self) -> None:
|
def _prepare_checked(self) -> None:
|
||||||
assets = self._editable_assets()
|
highlighted = bool(self.selected_ids)
|
||||||
|
assets = (
|
||||||
|
[asset for asset in self._selected_assets() if asset.has_plain]
|
||||||
|
if highlighted
|
||||||
|
else self._editable_assets()
|
||||||
|
)
|
||||||
if not assets:
|
if not assets:
|
||||||
|
message = (
|
||||||
|
"None of the highlighted images are in the editable folder. Decrypt them first "
|
||||||
|
"or clear the highlights to patch every editable image."
|
||||||
|
if highlighted
|
||||||
|
else "Decrypt images into the editable folder first."
|
||||||
|
)
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
"No Editable Images",
|
"No Editable Images",
|
||||||
"Decrypt images into the editable folder first.",
|
message,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if any(asset.has_encrypted for asset in assets) and not self._ensure_key():
|
if any(asset.has_encrypted for asset in assets) and not self._ensure_key():
|
||||||
return
|
return
|
||||||
|
scope = (
|
||||||
|
f"the {len(assets):,} highlighted editable image(s)"
|
||||||
|
if highlighted
|
||||||
|
else f"all {len(assets):,} editable image(s)"
|
||||||
|
)
|
||||||
answer = QMessageBox.question(
|
answer = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"Prepare Images for Patch",
|
"Prepare Images for Patch",
|
||||||
f"Encrypt and add all {len(assets):,} editable image(s) to the patch?\n\n"
|
f"Check {scope} and add changed images to the patch?\n\n"
|
||||||
"Encrypted images will be rebuilt from their editable PNG in .dazedtl/images. "
|
"Unchanged editable copies will be skipped. Changed encrypted images will be rebuilt "
|
||||||
"Their original encrypted "
|
"from .dazedtl/images. Their original encrypted "
|
||||||
"files are backed up once under .dazedtl/image_backups. Exact allow-rules are then "
|
"files are backed up once under .dazedtl/image_backups. Exact allow-rules are then "
|
||||||
"added to the applicable .gitignore files.",
|
"added to the applicable .gitignore files. Editable PNG copies will remain available "
|
||||||
|
"for further changes until you remove them.",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
QMessageBox.No,
|
QMessageBox.No,
|
||||||
)
|
)
|
||||||
|
|
@ -721,7 +771,8 @@ class RPGMakerImageManager(QWidget):
|
||||||
else:
|
else:
|
||||||
summary = (
|
summary = (
|
||||||
f"Prepared {result.completed:,} image(s) for the patch and updated "
|
f"Prepared {result.completed:,} image(s) for the patch and updated "
|
||||||
f"{len(result.gitignore_files):,} .gitignore file(s)."
|
f"{len(result.gitignore_files):,} .gitignore file(s); "
|
||||||
|
f"skipped {result.skipped:,} unchanged image(s)."
|
||||||
)
|
)
|
||||||
if result.errors:
|
if result.errors:
|
||||||
shown = "\n".join(result.errors[:12])
|
shown = "\n".join(result.errors[:12])
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,46 @@ class RPGMakerImageManagerSelectionTests(unittest.TestCase):
|
||||||
self.assertEqual(self.manager.image_list.count(), 0)
|
self.assertEqual(self.manager.image_list.count(), 0)
|
||||||
self.assertEqual(self.manager.remove_button.text(), "Remove highlighted")
|
self.assertEqual(self.manager.remove_button.text(), "Remove highlighted")
|
||||||
|
|
||||||
|
def test_prepare_uses_only_highlighted_editable_images(self):
|
||||||
|
highlighted = self.manager.assets[0]
|
||||||
|
not_highlighted = self.manager.assets[1]
|
||||||
|
for asset in (highlighted, not_highlighted):
|
||||||
|
asset.plain_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
asset.plain_path.write_bytes(asset.runtime_plain_path.read_bytes())
|
||||||
|
self._click(0)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(QMessageBox, "question", return_value=QMessageBox.Yes),
|
||||||
|
patch.object(self.manager, "_start_action") as start_action,
|
||||||
|
):
|
||||||
|
self.manager._prepare_checked()
|
||||||
|
|
||||||
|
action, assets = start_action.call_args.args
|
||||||
|
self.assertEqual(action, "prepare")
|
||||||
|
self.assertEqual(assets, [highlighted])
|
||||||
|
self.assertTrue(highlighted.plain_path.exists())
|
||||||
|
self.assertTrue(not_highlighted.plain_path.exists())
|
||||||
|
self.assertEqual(
|
||||||
|
self.manager.prepare_button.text(), "Encrypt highlighted + patch"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_prepare_uses_all_editable_images_without_highlights(self):
|
||||||
|
editable = self.manager.assets[:2]
|
||||||
|
for asset in editable:
|
||||||
|
asset.plain_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
asset.plain_path.write_bytes(asset.runtime_plain_path.read_bytes())
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(QMessageBox, "question", return_value=QMessageBox.Yes),
|
||||||
|
patch.object(self.manager, "_start_action") as start_action,
|
||||||
|
):
|
||||||
|
self.manager._prepare_checked()
|
||||||
|
|
||||||
|
action, assets = start_action.call_args.args
|
||||||
|
self.assertEqual(action, "prepare")
|
||||||
|
self.assertEqual(assets, editable)
|
||||||
|
self.assertEqual(self.manager.prepare_button.text(), "Encrypt all + patch")
|
||||||
|
|
||||||
def test_bottom_controls_share_one_row_and_action_width(self):
|
def test_bottom_controls_share_one_row_and_action_width(self):
|
||||||
action_buttons = (
|
action_buttons = (
|
||||||
self.manager.open_workspace_button,
|
self.manager.open_workspace_button,
|
||||||
|
|
@ -212,6 +252,21 @@ class RPGMakerImageManagerSelectionTests(unittest.TestCase):
|
||||||
self.manager.open_workspace_button.geometry().top(),
|
self.manager.open_workspace_button.geometry().top(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_open_folder_uses_highlighted_images_editable_parent(self):
|
||||||
|
self._click(0)
|
||||||
|
asset_id = self.manager.image_list.currentItem().data(Qt.UserRole + 1)
|
||||||
|
expected = self.manager.assets_by_id[asset_id].plain_path.parent
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"gui.rpgmaker_image_manager.QDesktopServices.openUrl",
|
||||||
|
return_value=True,
|
||||||
|
) as open_url:
|
||||||
|
self.manager._open_editable_folder()
|
||||||
|
|
||||||
|
opened_url = open_url.call_args.args[0]
|
||||||
|
self.assertEqual(Path(opened_url.toLocalFile()), expected)
|
||||||
|
self.assertTrue(expected.is_dir())
|
||||||
|
|
||||||
def test_thumbnail_batch_keeps_one_stable_tile_per_asset(self):
|
def test_thumbnail_batch_keeps_one_stable_tile_per_asset(self):
|
||||||
for worker in list(self.manager._thumbnail_workers):
|
for worker in list(self.manager._thumbnail_workers):
|
||||||
worker.wait(5000)
|
worker.wait(5000)
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ class RPGMakerImageTests(unittest.TestCase):
|
||||||
self.assertEqual(result.completed, 1)
|
self.assertEqual(result.completed, 1)
|
||||||
self.assertEqual(result.errors, [])
|
self.assertEqual(result.errors, [])
|
||||||
self.assertEqual(decrypt_image_bytes(encrypted.read_bytes(), KEY), translated)
|
self.assertEqual(decrypt_image_bytes(encrypted.read_bytes(), KEY), translated)
|
||||||
|
self.assertEqual(asset.plain_path.read_bytes(), translated)
|
||||||
backup = self.root / ".dazedtl" / "image_backups" / "www/img/pictures/001.rpgmvp"
|
backup = self.root / ".dazedtl" / "image_backups" / "www/img/pictures/001.rpgmvp"
|
||||||
self.assertEqual(backup.read_bytes(), original)
|
self.assertEqual(backup.read_bytes(), original)
|
||||||
ignore = self.root.joinpath(".gitignore").read_text(encoding="utf-8")
|
ignore = self.root.joinpath(".gitignore").read_text(encoding="utf-8")
|
||||||
|
|
@ -123,11 +124,47 @@ class RPGMakerImageTests(unittest.TestCase):
|
||||||
self.assertIn("!/www/img/pictures/001.rpgmvp", ignore)
|
self.assertIn("!/www/img/pictures/001.rpgmvp", ignore)
|
||||||
self.assertNotIn("!/www/img/pictures/001.png", ignore)
|
self.assertNotIn("!/www/img/pictures/001.png", ignore)
|
||||||
|
|
||||||
prepare_assets_for_patch(self.root, [asset], KEY)
|
second = prepare_assets_for_patch(self.root, [asset], KEY)
|
||||||
again = self.root.joinpath(".gitignore").read_text(encoding="utf-8")
|
again = self.root.joinpath(".gitignore").read_text(encoding="utf-8")
|
||||||
|
self.assertEqual(second.completed, 0)
|
||||||
|
self.assertEqual(second.skipped, 1)
|
||||||
self.assertEqual(again.count("!/www/img/pictures/001.rpgmvp"), 1)
|
self.assertEqual(again.count("!/www/img/pictures/001.rpgmvp"), 1)
|
||||||
self.assertEqual(backup.read_bytes(), original)
|
self.assertEqual(backup.read_bytes(), original)
|
||||||
|
|
||||||
|
def test_prepare_skips_unchanged_decrypted_image(self):
|
||||||
|
encrypted = self._encrypted_asset()
|
||||||
|
original = encrypted.read_bytes()
|
||||||
|
asset = scan_image_assets(self.root)[0]
|
||||||
|
decrypt_assets([asset], KEY, game_root=self.root)
|
||||||
|
|
||||||
|
result = prepare_assets_for_patch(self.root, [asset], KEY)
|
||||||
|
|
||||||
|
self.assertEqual(result.completed, 0)
|
||||||
|
self.assertEqual(result.skipped, 1)
|
||||||
|
self.assertEqual(result.patch_files, [])
|
||||||
|
self.assertEqual(encrypted.read_bytes(), original)
|
||||||
|
self.assertFalse(
|
||||||
|
(self.root / ".dazedtl" / "image_backups" / "www/img/pictures/001.rpgmvp").exists()
|
||||||
|
)
|
||||||
|
ignore = self.root.joinpath(".gitignore").read_text(encoding="utf-8")
|
||||||
|
self.assertNotIn("!/www/img/pictures/001.rpgmvp", ignore)
|
||||||
|
|
||||||
|
def test_prepare_skips_unchanged_unencrypted_workspace_copy(self):
|
||||||
|
runtime = self.content / "img" / "pictures" / "menu.png"
|
||||||
|
original = png_bytes("yellow")
|
||||||
|
runtime.write_bytes(original)
|
||||||
|
asset = scan_image_assets(self.root)[0]
|
||||||
|
asset.plain_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
asset.plain_path.write_bytes(original)
|
||||||
|
|
||||||
|
result = prepare_assets_for_patch(self.root, [asset], None)
|
||||||
|
|
||||||
|
self.assertEqual(result.completed, 0)
|
||||||
|
self.assertEqual(result.skipped, 1)
|
||||||
|
self.assertEqual(result.patch_files, [])
|
||||||
|
self.assertEqual(runtime.read_bytes(), original)
|
||||||
|
self.assertFalse((self.root / ".dazedtl" / "image_backups").exists())
|
||||||
|
|
||||||
def test_mz_png_uses_same_crypto_and_logical_png_name(self):
|
def test_mz_png_uses_same_crypto_and_logical_png_name(self):
|
||||||
encrypted = self._encrypted_asset("title.png_", "green")
|
encrypted = self._encrypted_asset("title.png_", "green")
|
||||||
asset = scan_image_assets(self.root)[0]
|
asset = scan_image_assets(self.root)[0]
|
||||||
|
|
|
||||||
|
|
@ -518,6 +518,13 @@ def prepare_assets_for_patch(
|
||||||
raise FileNotFoundError("decrypt this image before preparing it")
|
raise FileNotFoundError("decrypt this image before preparing it")
|
||||||
if key is None:
|
if key is None:
|
||||||
raise ValueError("the encryption key is required")
|
raise ValueError("the encryption key is required")
|
||||||
|
if progress:
|
||||||
|
progress(f"Checking {asset.asset_id}")
|
||||||
|
plain_bytes = asset.plain_path.read_bytes()
|
||||||
|
runtime_bytes = asset.encrypted_path.read_bytes()
|
||||||
|
if decrypt_image_bytes(runtime_bytes, key) == plain_bytes:
|
||||||
|
result.skipped += 1
|
||||||
|
continue
|
||||||
relative_encrypted = _path_inside(root, asset.encrypted_path)
|
relative_encrypted = _path_inside(root, asset.encrypted_path)
|
||||||
backup = root / ".dazedtl" / "image_backups" / relative_encrypted
|
backup = root / ".dazedtl" / "image_backups" / relative_encrypted
|
||||||
if not backup.exists():
|
if not backup.exists():
|
||||||
|
|
@ -525,7 +532,7 @@ def prepare_assets_for_patch(
|
||||||
shutil.copy2(asset.encrypted_path, backup)
|
shutil.copy2(asset.encrypted_path, backup)
|
||||||
if progress:
|
if progress:
|
||||||
progress(f"Encrypting {asset.asset_id}")
|
progress(f"Encrypting {asset.asset_id}")
|
||||||
encrypted = encrypt_image_bytes(asset.plain_path.read_bytes(), key)
|
encrypted = encrypt_image_bytes(plain_bytes, key)
|
||||||
_atomic_write(asset.encrypted_path, encrypted)
|
_atomic_write(asset.encrypted_path, encrypted)
|
||||||
targets.append(asset.encrypted_path)
|
targets.append(asset.encrypted_path)
|
||||||
elif asset.has_runtime_plain:
|
elif asset.has_runtime_plain:
|
||||||
|
|
@ -533,6 +540,12 @@ def prepare_assets_for_patch(
|
||||||
# If the user made a workspace copy, publish it back there and
|
# If the user made a workspace copy, publish it back there and
|
||||||
# preserve the original once, just like encrypted assets.
|
# preserve the original once, just like encrypted assets.
|
||||||
if asset.has_plain:
|
if asset.has_plain:
|
||||||
|
if progress:
|
||||||
|
progress(f"Checking {asset.asset_id}")
|
||||||
|
plain_bytes = asset.plain_path.read_bytes()
|
||||||
|
if asset.runtime_plain_path.read_bytes() == plain_bytes:
|
||||||
|
result.skipped += 1
|
||||||
|
continue
|
||||||
relative_plain = _path_inside(root, asset.runtime_plain_path)
|
relative_plain = _path_inside(root, asset.runtime_plain_path)
|
||||||
backup = root / ".dazedtl" / "image_backups" / relative_plain
|
backup = root / ".dazedtl" / "image_backups" / relative_plain
|
||||||
if not backup.exists():
|
if not backup.exists():
|
||||||
|
|
@ -540,9 +553,7 @@ def prepare_assets_for_patch(
|
||||||
shutil.copy2(asset.runtime_plain_path, backup)
|
shutil.copy2(asset.runtime_plain_path, backup)
|
||||||
if progress:
|
if progress:
|
||||||
progress(f"Publishing {asset.asset_id}")
|
progress(f"Publishing {asset.asset_id}")
|
||||||
_atomic_write(
|
_atomic_write(asset.runtime_plain_path, plain_bytes)
|
||||||
asset.runtime_plain_path, asset.plain_path.read_bytes()
|
|
||||||
)
|
|
||||||
targets.append(asset.runtime_plain_path)
|
targets.append(asset.runtime_plain_path)
|
||||||
else:
|
else:
|
||||||
raise FileNotFoundError("image file no longer exists")
|
raise FileNotFoundError("image file no longer exists")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue