From ffa3cbc1db13493fa17cd43b7de928c737cf8bf5 Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Wed, 7 May 2025 16:14:51 +0100 Subject: [PATCH 1/4] Restore for Cubase and Audition backups --- app/controllers/ProjectEntryController.scala | 78 ++++++++++++++++++ conf/routes | 1 + .../AssetFolderProjectBackups.tsx | 79 ++++++++++++++++++- 3 files changed, 157 insertions(+), 1 deletion(-) diff --git a/app/controllers/ProjectEntryController.scala b/app/controllers/ProjectEntryController.scala index 5c3811b8..64127c24 100644 --- a/app/controllers/ProjectEntryController.scala +++ b/app/controllers/ProjectEntryController.scala @@ -1184,4 +1184,82 @@ class ProjectEntryController @Inject() (@Named("project-creation-actor") project } }) }} + + def deleteRecursively(file: File): Unit = { + if (file.isDirectory) { + file.listFiles.foreach(deleteRecursively) + } + if (file.exists && !file.delete) { + throw new Exception(s"Unable to delete ${file.getAbsolutePath}") + } + } + + def restoreAssetFolderBackup(requestedId: Int) = IsAuthenticatedAsync {uid=>{request=> + implicit val db = dbConfig.db + + selectid(requestedId).flatMap({ + case Failure(error)=> + logger.error(s"Could not restore files for project ${requestedId}",error) + Future(InternalServerError(Json.obj("status"->"error","detail"->error.toString))) + case Success(someSeq)=> + someSeq.headOption match { + case Some(projectEntry)=> + db.run( + TableQuery[ProjectMetadataRow] + .filter(_.key===ProjectMetadata.ASSET_FOLDER_KEY) + .filter(_.projectRef===requestedId) + .result + ).map(results=>{ + val resultCount = results.length + if(resultCount==0){ + logger.warn(s"No asset folder registered under project id $requestedId") + } else if(resultCount>1){ + logger.warn(s"Multiple asset folders found for project $requestedId: $results") + } else { + logger.debug(s"Found this data: ${results.head}") + logger.debug(s"Found this asset folder: ${results.head.value.get}") + deleteRecursively(new File(s"${results.head.value.get}/RestoredProjectFiles")) + new File(s"${results.head.value.get}/RestoredProjectFiles").mkdirs() + val fileData = for { + f2 <- projectEntry.associatedAssetFolderFiles(false, implicitConfig).map(fileList=>fileList) + } yield (f2) + val fileEntryData = Await.result(fileData, Duration(10, TimeUnit.SECONDS)) + logger.debug(s"File data found: $fileEntryData") + val splitterRegex = "^(?:[^\\/]*\\/){4}".r + val filenameRegex = "([^\\/]+$)".r + fileEntryData.map(fileData => { + new File(s"${config.get[String]("postrun.assetFolder.basePath")}/${splitterRegex.findFirstIn(fileData.filepath).get}RestoredProjectFiles/${filenameRegex.replaceFirstIn(splitterRegex.replaceFirstIn(fileData.filepath,""),"")}").mkdirs() + val timestamp = dateTimeToTimestamp(ZonedDateTime.now()) + if (new File(s"${config.get[String]("postrun.assetFolder.basePath")}/${splitterRegex.findFirstIn(fileData.filepath).get}RestoredProjectFiles/${splitterRegex.replaceFirstIn(fileData.filepath,"")}").exists()) { + var space_not_found = true + var number_to_try = 1 + while (space_not_found) { + val pathToWorkOn = splitterRegex.replaceFirstIn(fileData.filepath,"") + val indexOfPoint = pathToWorkOn.lastIndexOf(".") + val readyPath = s"${pathToWorkOn.substring(0, indexOfPoint)}_$number_to_try${pathToWorkOn.substring(indexOfPoint)}" + if (new File(s"${config.get[String]("postrun.assetFolder.basePath")}/${splitterRegex.findFirstIn(fileData.filepath).get}RestoredProjectFiles/$readyPath").exists()) { + number_to_try = number_to_try + 1 + } else { + space_not_found = false + val fileToSave = AssetFolderFileEntry(None, s"${splitterRegex.findFirstIn(fileData.filepath).get}RestoredProjectFiles/$readyPath", config.get[Int]("asset_folder_storage"), 1, timestamp, timestamp, timestamp, None, None) + storageHelper.copyAssetFolderFile(fileData, fileToSave) + } + } + } else { + val fileToSave = AssetFolderFileEntry(None, s"${splitterRegex.findFirstIn(fileData.filepath).get}RestoredProjectFiles/${splitterRegex.replaceFirstIn(fileData.filepath, "")}", config.get[Int]("asset_folder_storage"), 1, timestamp, timestamp, timestamp, None, None) + storageHelper.copyAssetFolderFile(fileData, fileToSave) + } + } + ) + } + }).recover({ + case err: Throwable => + logger.error(s"Could not look up asset folder for project id $requestedId: ", err) + }) + Future(Ok(Json.obj("status"->"okay","detail"->s"Restored files for project $requestedId"))) + case None=> + Future(NotFound(Json.obj("status"->"error","detail"->s"Project $requestedId not found"))) + } + }) + }} } diff --git a/conf/routes b/conf/routes index 6ef3916d..09434224 100644 --- a/conf/routes +++ b/conf/routes @@ -69,6 +69,7 @@ GET /api/project/:id/missingFiles @controllers.MissingFilesController. GET /api/project/:id/removeWarning @controllers.MissingFilesController.removeWarning(id:Int) GET /api/project/:id/fileDownload @controllers.ProjectEntryController.fileDownload(id:Int) PUT /api/project/:id/restore/:version @controllers.ProjectEntryController.restoreBackup(id:Int, version:Int) +PUT /api/project/:id/restoreForAssetFolder @controllers.ProjectEntryController.restoreAssetFolderBackup(id:Int) GET /api/valid-users @controllers.ProjectEntryController.queryUsersForAutocomplete(prefix:String ?= "", limit:Option[Int]) GET /api/known-user @controllers.ProjectEntryController.isUserKnown(uname:String ?= "") diff --git a/frontend/app/ProjectEntryList/AssetFolderProjectBackups.tsx b/frontend/app/ProjectEntryList/AssetFolderProjectBackups.tsx index 127d3158..a1da10fd 100644 --- a/frontend/app/ProjectEntryList/AssetFolderProjectBackups.tsx +++ b/frontend/app/ProjectEntryList/AssetFolderProjectBackups.tsx @@ -13,6 +13,7 @@ import { Paper, Tooltip, Typography, + DialogContent, } from "@material-ui/core"; import { Breadcrumb } from "@guardian/pluto-headers"; import { ArrowBack, PermMedia, WarningRounded } from "@material-ui/icons"; @@ -20,6 +21,11 @@ import { getProject, getAssetFolderProjectFiles } from "./helpers"; import clsx from "clsx"; import AssetFolderBackupEntry from "./AssetFolderBackupEntry"; import { useGuardianStyles } from "~/misc/utils"; +import axios from "axios"; +import { + SystemNotification, + SystemNotifcationKind, +} from "@guardian/pluto-headers"; declare var deploymentRootPath: string; @@ -30,7 +36,7 @@ const AssetFolderProjectBackups: React.FC( undefined ); - + const [openDialog, setOpenDialog] = useState(false); const [backupFiles, setBackupFiles] = useState([]); const history = useHistory(); const classes = useGuardianStyles(); @@ -61,6 +67,42 @@ const AssetFolderProjectBackups: React.FC { + setOpenDialog(true); + }; + + const handleCloseDialog = () => { + setOpenDialog(false); + }; + + const handleConfirmUpload = () => { + handleCloseDialog(); + handleRestore(); + }; + + const handleRestore = async () => { + try { + const request = + "/api/project/" + props.match.params.itemid + "/restoreForAssetFolder"; + const response = await axios.put(request, null, { + headers: { + "Content-Type": "application/json", + }, + }); + console.log(response.data); + SystemNotification.open( + SystemNotifcationKind.Success, + `${response.data.detail}` + ); + } catch (error) { + console.error("Error restoring file:", error); + SystemNotification.open( + SystemNotifcationKind.Error, + `Failed to restore project: ${error}` + ); + } + }; + return ( <> {project ? ( @@ -81,6 +123,41 @@ const AssetFolderProjectBackups: React.FC ) : undefined} + + + {/* Confirmation Dialog */} + + + Confirm Restoration of Backed up Project Files: + + + + You are about to restore all the backed up project files shown on this page. This will result in a folder being created in the project's asset folder named "RestoredProjectFiles", and within that a list of all Cubase/Audition files we have backed up. Once you have identified which project file you need, please move it out of this folder and into the root of project’s asset folder, before you carry on working with it. +
+
+
+
+ + + + +
+
From 394e86052246693aaeb7587fb3489f0a91c8157d Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Wed, 7 May 2025 16:19:42 +0100 Subject: [PATCH 2/4] Changes suggested by Yarn --- .../AssetFolderProjectBackups.tsx | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/frontend/app/ProjectEntryList/AssetFolderProjectBackups.tsx b/frontend/app/ProjectEntryList/AssetFolderProjectBackups.tsx index a1da10fd..e577809f 100644 --- a/frontend/app/ProjectEntryList/AssetFolderProjectBackups.tsx +++ b/frontend/app/ProjectEntryList/AssetFolderProjectBackups.tsx @@ -83,7 +83,7 @@ const AssetFolderProjectBackups: React.FC { try { const request = - "/api/project/" + props.match.params.itemid + "/restoreForAssetFolder"; + "/api/project/" + props.match.params.itemid + "/restoreForAssetFolder"; const response = await axios.put(request, null, { headers: { "Content-Type": "application/json", @@ -91,14 +91,14 @@ const AssetFolderProjectBackups: React.FC {/* Confirmation Dialog */} Confirm Restoration of Backed up Project Files: - You are about to restore all the backed up project files shown on this page. This will result in a folder being created in the project's asset folder named "RestoredProjectFiles", and within that a list of all Cubase/Audition files we have backed up. Once you have identified which project file you need, please move it out of this folder and into the root of project’s asset folder, before you carry on working with it. + You are about to restore all the backed up project files shown + on this page. This will result in a folder being created in the + project's asset folder named "RestoredProjectFiles", and within + that a list of all Cubase/Audition files we have backed up. Once + you have identified which project file you need, please move it + out of this folder and into the root of project’s asset folder, + before you carry on working with it.

From 5c50fb7397a6f69f9f0a7105c66b380450414930 Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Thu, 8 May 2025 11:20:16 +0100 Subject: [PATCH 3/4] Pause for four seconds --- app/controllers/ProjectEntryController.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/ProjectEntryController.scala b/app/controllers/ProjectEntryController.scala index 64127c24..63287a48 100644 --- a/app/controllers/ProjectEntryController.scala +++ b/app/controllers/ProjectEntryController.scala @@ -1228,6 +1228,7 @@ class ProjectEntryController @Inject() (@Named("project-creation-actor") project val splitterRegex = "^(?:[^\\/]*\\/){4}".r val filenameRegex = "([^\\/]+$)".r fileEntryData.map(fileData => { + Thread.sleep(4000) new File(s"${config.get[String]("postrun.assetFolder.basePath")}/${splitterRegex.findFirstIn(fileData.filepath).get}RestoredProjectFiles/${filenameRegex.replaceFirstIn(splitterRegex.replaceFirstIn(fileData.filepath,""),"")}").mkdirs() val timestamp = dateTimeToTimestamp(ZonedDateTime.now()) if (new File(s"${config.get[String]("postrun.assetFolder.basePath")}/${splitterRegex.findFirstIn(fileData.filepath).get}RestoredProjectFiles/${splitterRegex.replaceFirstIn(fileData.filepath,"")}").exists()) { From 0c5a58e96417f95e57d2cf7c33b745593104fbd0 Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Thu, 8 May 2025 11:32:33 +0100 Subject: [PATCH 4/4] Changing pause time to a tenth of a second --- app/controllers/ProjectEntryController.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/ProjectEntryController.scala b/app/controllers/ProjectEntryController.scala index 63287a48..cedf83bb 100644 --- a/app/controllers/ProjectEntryController.scala +++ b/app/controllers/ProjectEntryController.scala @@ -1228,7 +1228,7 @@ class ProjectEntryController @Inject() (@Named("project-creation-actor") project val splitterRegex = "^(?:[^\\/]*\\/){4}".r val filenameRegex = "([^\\/]+$)".r fileEntryData.map(fileData => { - Thread.sleep(4000) + Thread.sleep(100) new File(s"${config.get[String]("postrun.assetFolder.basePath")}/${splitterRegex.findFirstIn(fileData.filepath).get}RestoredProjectFiles/${filenameRegex.replaceFirstIn(splitterRegex.replaceFirstIn(fileData.filepath,""),"")}").mkdirs() val timestamp = dateTimeToTimestamp(ZonedDateTime.now()) if (new File(s"${config.get[String]("postrun.assetFolder.basePath")}/${splitterRegex.findFirstIn(fileData.filepath).get}RestoredProjectFiles/${splitterRegex.replaceFirstIn(fileData.filepath,"")}").exists()) {