Skip to content

Commit

Permalink
Merge pull request #403 from skaut/error-reporting
Browse files Browse the repository at this point in the history
Reporting encountered errors
  • Loading branch information
marekdedic authored Dec 22, 2020
2 parents c1e23a8 + f2b7ed4 commit 796b0d0
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 63 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"globals": {
"google": "readonly",
"ListResponse": "readonly",
"MoveError": "readonly",
"MoveResponse": "readonly",
"NamedRecord": "readonly"
}
Expand Down
176 changes: 116 additions & 60 deletions src/backend/move.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* exported move */

function isSharedDriveEmpty(
sharedDrive: string,
function isDirectoryEmpty(
directoryID: string,
notEmptyOverride: boolean
): boolean {
if (notEmptyOverride) {
Expand All @@ -10,28 +10,32 @@ function isSharedDriveEmpty(
const response = Drive.Files!.list({
includeItemsFromAllDrives: true,
maxResults: 1,
q: '"' + sharedDrive + '" in parents and trashed = false',
q: '"' + directoryID + '" in parents and trashed = false',
supportsAllDrives: true,
fields: "items(id)",
});
return response.items!.length === 0;
}

function moveFile(file: string, source: string, destination: string): void {
Drive.Files!.update({}, file, null, {
addParents: destination,
removeParents: source,
function moveFile(
fileID: string,
sourceID: string,
destinationID: string
): void {
Drive.Files!.update({}, fileID, null, {
addParents: destinationID,
removeParents: sourceID,
supportsAllDrives: true,
fields: "",
});
}

function copyFileComments(source: string, destination: string): void {
function copyFileComments(sourceID: string, destinationID: string): void {
const comments = [];
let pageToken = null;
do {
const response: GoogleAppsScript.Drive.Schema.CommentList = Drive.Comments!.list(
source,
sourceID,
{
maxResults: 100,
pageToken: pageToken,
Expand All @@ -51,48 +55,58 @@ function copyFileComments(source: string, destination: string): void {
}
const replies = comment.replies!;
delete comment.replies;
const commentId = Drive.Comments!.insert(comment, destination).commentId!;
const commentId = Drive.Comments!.insert(comment, destinationID).commentId!;
for (const reply of replies) {
if (!reply.author!.isAuthenticatedUser) {
reply.content =
"*" + reply.author!.displayName! + ":*\n" + reply.content!;
}
Drive.Replies!.insert(reply, destination, commentId);
Drive.Replies!.insert(reply, destinationID, commentId);
}
}
}

function moveFileByCopy(
file: string,
fileID: string,
name: string,
destination: string,
destinationID: string,
path: Array<string>,
copyComments: boolean
): void {
const copy = Drive.Files!.copy(
{
parents: [{ id: destination }],
title: name,
},
file,
{ supportsAllDrives: true, fields: "id" }
);
if (copyComments) {
copyFileComments(file, copy.id!);
): MoveError | null {
try {
const copy = Drive.Files!.copy(
{
parents: [{ id: destinationID }],
title: name,
},
fileID,
{ supportsAllDrives: true, fields: "id" }
);
if (copyComments) {
copyFileComments(fileID, copy.id!);
}
return null;
} catch (e) {
return {
file: path.concat([name]),
error: (e as GoogleJsonResponseException).message,
};
}
}

function moveFolderContentsFiles(
source: string,
destination: string,
sourceID: string,
destinationID: string,
path: Array<string>,
copyComments: boolean
): void {
): Array<MoveError> {
const files = [];
let pageToken = null;
do {
const response: GoogleAppsScript.Drive.Schema.FileList = Drive.Files!.list({
q:
'"' +
source +
sourceID +
'" in parents and mimeType != "application/vnd.google-apps.folder" and trashed = false',
pageToken: pageToken,
maxResults: 1000,
Expand All @@ -108,50 +122,69 @@ function moveFolderContentsFiles(
}
pageToken = response.nextPageToken;
} while (pageToken !== undefined);
const errors: Array<MoveError> = [];
for (const file of files) {
let error = null;
if (file.canMove) {
try {
moveFile(file.id!, source, destination);
moveFile(file.id!, sourceID, destinationID);
} catch (_) {
moveFileByCopy(file.id!, file.name!, destination, copyComments);
error = moveFileByCopy(
file.id!,
file.name!,
destinationID,
path,
copyComments
);
}
} else {
moveFileByCopy(file.id!, file.name!, destination, copyComments);
error = moveFileByCopy(
file.id!,
file.name!,
destinationID,
path,
copyComments
);
}
if (error !== null) {
errors.push(error);
}
}
return errors;
}

function deleteFolderIfEmpty(folder: string): void {
function deleteFolderIfEmpty(folderID: string): void {
const response = Drive.Files!.list({
maxResults: 1,
q: '"' + folder + '" in parents and trashed = false',
q: '"' + folderID + '" in parents and trashed = false',
fields: "items(id)",
});
if (response.items!.length === 0) {
const response2 = Drive.Files!.get(folder, {
const response2 = Drive.Files!.get(folderID, {
fields: "userPermission(role)",
});
if (
response2.userPermission!.role === "owner" ||
response2.userPermission!.role === "organizer"
) {
Drive.Files!.remove(folder);
Drive.Files!.remove(folderID);
}
}
}

function moveFolderContentsFolders(
source: string,
destination: string,
sourceID: string,
destinationID: string,
path: Array<string>,
copyComments: boolean
): void {
): Array<MoveError> {
const folders = [];
let pageToken = null;
do {
const response: GoogleAppsScript.Drive.Schema.FileList = Drive.Files!.list({
q:
'"' +
source +
sourceID +
'" in parents and mimeType = "application/vnd.google-apps.folder" and trashed = false',
pageToken: pageToken,
maxResults: 1000,
Expand All @@ -162,39 +195,62 @@ function moveFolderContentsFolders(
}
pageToken = response.nextPageToken;
} while (pageToken !== undefined);
let errors: Array<MoveError> = [];
for (const folder of folders) {
const newFolder = Drive.Files!.insert(
{
parents: [{ id: destination }],
title: folder.name,
mimeType: "application/vnd.google-apps.folder",
},
undefined,
{ supportsAllDrives: true, fields: "id" }
);
moveFolderContents(folder.id!, newFolder.id!, copyComments); // eslint-disable-line @typescript-eslint/no-use-before-define
deleteFolderIfEmpty(folder.id!);
try {
const newFolder = Drive.Files!.insert(
{
parents: [{ id: destinationID }],
title: folder.name,
mimeType: "application/vnd.google-apps.folder",
},
undefined,
{ supportsAllDrives: true, fields: "id" }
);
errors = errors.concat(
moveFolderContents(
folder.id!,
newFolder.id!,
path.concat([folder.name!]),
copyComments
)
);
deleteFolderIfEmpty(folder.id!);
} catch (e) {
errors.push({ file: path.concat([folder.name!]), error: e as string });
}
}
return errors;
}

function moveFolderContents(
source: string,
destination: string,
sourceID: string,
destinationID: string,
path: Array<string>,
copyComments: boolean
): void {
moveFolderContentsFiles(source, destination, copyComments);
moveFolderContentsFolders(source, destination, copyComments);
): Array<MoveError> {
return moveFolderContentsFiles(
sourceID,
destinationID,
path,
copyComments
).concat(
moveFolderContentsFolders(sourceID, destinationID, path, copyComments)
);
}

function move(
folder: string,
sharedDrive: string,
sourceID: string,
destinationID: string,
copyComments: boolean,
notEmptyOverride: boolean
): MoveResponse {
if (!isSharedDriveEmpty(sharedDrive, notEmptyOverride)) {
if (!isDirectoryEmpty(destinationID, notEmptyOverride)) {
return { status: "error", reason: "notEmpty" };
}
moveFolderContents(folder, sharedDrive, copyComments);
return { status: "success" };
const errors = moveFolderContents(sourceID, destinationID, [], copyComments);
if (errors.length > 0) {
console.error(errors);
}
return { status: "success", errors };
}
4 changes: 3 additions & 1 deletion src/frontend/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
<InProgress />
</md-step>
<md-step id="done" :md-label="$t('steps.done')">
<Done />
<Done :errors="errors" />
</md-step>
</md-steppers>
<md-dialog-confirm
Expand Down Expand Up @@ -106,6 +106,7 @@ export default Vue.extend({
displayNonEmptyDialog: false,
displayErrorDialog: false,
optionalErrorMessage: "",
errors: [] as Array<MoveError>,
};
},
created() {
Expand Down Expand Up @@ -198,6 +199,7 @@ export default Vue.extend({
this.displayNonEmptyDialog = true;
}
} else {
this.errors = response.errors!;
this.activeStep = "done";
}
},
Expand Down
37 changes: 35 additions & 2 deletions src/frontend/Done.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
<template>
<div>
{{ $t("done.description") }}
<p>
{{ $t("done.description") }}
</p>
<div v-if="errors.length > 0">
<p class="md-body-2">
<md-icon class="md-accent">error</md-icon>
<i18n path="done.errorHeader.text">
<template #link>
<a
href="https://github.com/skaut/shared-drive-mover/issues"
target="_blank"
>{{ $t("done.errorHeader.github") }}</a
>
</template>
</i18n>
</p>
<md-table>
<md-table-row>
<md-table-head> {{ $t("done.file") }} </md-table-head>
<md-table-head> {{ $t("done.message") }} </md-table-head>
</md-table-row>
<md-table-row v-for="error in errors" :key="error.file">
<md-table-cell> {{ error.file.join("/") }} </md-table-cell>
<md-table-cell> {{ error.error }} </md-table-cell>
</md-table-row>
</md-table>
</div>
</div>
</template>

<script lang="ts">
import Vue from "vue";
export default Vue.extend({});
export default Vue.extend({
props: {
errors: {
type: Array,
default: (): Array<string> => [],
},
},
});
</script>

<style scoped>
Expand Down
Loading

0 comments on commit 796b0d0

Please sign in to comment.