Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions app/controllers/ProjectEntryController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1184,4 +1184,83 @@ 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 => {
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()) {
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")))
}
})
}}
}
1 change: 1 addition & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?= "")
Expand Down
85 changes: 84 additions & 1 deletion frontend/app/ProjectEntryList/AssetFolderProjectBackups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ import {
Paper,
Tooltip,
Typography,
DialogContent,
} from "@material-ui/core";
import { Breadcrumb } from "@guardian/pluto-headers";
import { ArrowBack, PermMedia, WarningRounded } from "@material-ui/icons";
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;

Expand All @@ -30,7 +36,7 @@ const AssetFolderProjectBackups: React.FC<RouteComponentProps<{
const [dialogErrString, setDialogErrString] = useState<string | undefined>(
undefined
);

const [openDialog, setOpenDialog] = useState(false);
const [backupFiles, setBackupFiles] = useState<AssetFolderFileEntry[]>([]);
const history = useHistory();
const classes = useGuardianStyles();
Expand Down Expand Up @@ -61,6 +67,42 @@ const AssetFolderProjectBackups: React.FC<RouteComponentProps<{
}
}, [project]);

const handleClickOpenDialog = () => {
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 ? (
Expand All @@ -81,6 +123,47 @@ const AssetFolderProjectBackups: React.FC<RouteComponentProps<{
/>
) : undefined}
</Grid>
<Grid item>
<Button
color="secondary"
variant="contained"
onClick={handleClickOpenDialog}
>
Restore
</Button>
{/* Confirmation Dialog */}
<Dialog
open={openDialog}
onClose={handleCloseDialog}
aria-labelledby="update-file-dialog-title"
aria-describedby="update-file-dialog-description"
>
<DialogTitle id="update-file-dialog-title">
Confirm Restoration of Backed up Project Files:
</DialogTitle>
<DialogContent>
<DialogContentText id="update-file-dialog-description">
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.
<br />
<br />
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseDialog} color="primary">
Cancel
</Button>
<Button onClick={handleConfirmUpload} color="primary" autoFocus>
Proceed
</Button>
</DialogActions>
</Dialog>
</Grid>
<Grid item>
<Grid container spacing={2}>
<Grid item>
Expand Down
Loading