Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[full-ci] Resolve upload existing folder conflict dialog #7504

Merged
merged 29 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6973a0c
It aint much but it kinda works
lookacat Aug 22, 2022
af74fba
Implement "keep both"
lookacat Aug 23, 2022
21143ab
Add changelog
lookacat Aug 23, 2022
f08b9f8
remove dev leftover
lookacat Aug 23, 2022
7b71a91
Fix folder name
lookacat Aug 23, 2022
133cc2b
Add isFolder
lookacat Aug 26, 2022
81c53f1
Make file conflict dialog work
lookacat Aug 31, 2022
9994412
Linting
lookacat Aug 31, 2022
1380d73
Fix folder keep both
lookacat Aug 31, 2022
21ace2c
Check for folder to already exist
lookacat Aug 31, 2022
9c43891
remove dev leftover
lookacat Aug 31, 2022
50b67f3
Address PR issues
lookacat Sep 2, 2022
20f72fb
Use store
lookacat Sep 5, 2022
9d98ed1
Provide existing files with function parameter
lookacat Sep 5, 2022
5f772f5
Add type to interface
lookacat Sep 5, 2022
f3649d5
Refactor resolve file & folder conflicts
lookacat Sep 6, 2022
fa29985
Refactor conflict dialog
lookacat Sep 6, 2022
d2408cd
Bugfix, remove dev leftover
lookacat Sep 6, 2022
1f217ca
Simplify conflict-array structure
JammingBen Sep 6, 2022
fade050
Fix folder upload
JammingBen Sep 6, 2022
dc7615d
Add merge to folders
lookacat Sep 6, 2022
c040bdd
Ignore existing folder errors for now
lookacat Sep 6, 2022
9179c7e
Make Merge reappear if "do for all" ticked
lookacat Sep 7, 2022
4511d5b
Address PR issues
lookacat Sep 7, 2022
afedbd7
Add unittests
lookacat Sep 7, 2022
9a0d3e2
Add more unittests
lookacat Sep 7, 2022
d79fe54
Fix e2e upload version
lookacat Sep 7, 2022
b4fe000
Fix file overwrite acceptance tests
lookacat Sep 8, 2022
4ac0d5a
Address PR issues
lookacat Sep 9, 2022
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
6 changes: 6 additions & 0 deletions changelog/unreleased/bugfix-resolve-upload-existing-folder
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Bugfix: Resolve upload existing folder

We've added an conflict dialog when uploading files and folders
lookacat marked this conversation as resolved.
Show resolved Hide resolved

https://github.com/owncloud/web/pull/7504
https://github.com/owncloud/web/issues/6996
140 changes: 81 additions & 59 deletions packages/web-app-files/src/components/AppBar/CreateAndUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,14 @@ import { UppyResource, useUpload } from 'web-runtime/src/composables/upload'
import { useUploadHelpers } from '../../composables/upload'
import { SHARE_JAIL_ID } from '../../services/folder'
import { bus } from 'web-pkg/src/instance'
import { buildWebDavSpacesPath } from 'web-client/src/helpers'
import { resolveFileNameDuplicate, extractNameWithoutExtension } from '../../helpers/resource'
import { buildWebDavSpacesPath, Resource } from 'web-client/src/helpers'
import { extractExtensionFromFile, extractNameWithoutExtension } from '../../helpers/resource'
import {
resolveFileExists,
ResolveStrategy,
ResolveConflict,
resolveFileNameDuplicate
} from '../../helpers/resource/copyMove'

export default defineComponent({
components: {
Expand Down Expand Up @@ -678,43 +684,67 @@ export default defineComponent({
return null
},

onFilesSelected(files: File[]) {
async onFilesSelected(files: File[]) {
const conflicts = []
const uppyResources: UppyResource[] = this.inputFilesToUppyFiles(files)
const quotaExceeded = this.checkQuotaExceeded(uppyResources)

if (quotaExceeded) {
return this.$uppyService.clearInputs()
}

const resolveStrategies = {}
for (const file of uppyResources) {
const relativeFilePath = file.meta.relativePath
if (relativeFilePath) {
// Logic for folders, applies to all files inside folder and subfolders
const rootFolder = relativeFilePath.replace(/^\/+/, '').split('/')[0]
const exists = this.files.find((f) => f.name === rootFolder)
if (exists) {
this.showMessage({
title: this.$gettextInterpolate(
this.$gettext('Folder "%{folder}" already exists.'),
{ folder: rootFolder },
if (!(rootFolder in resolveStrategies)) {
const resolveStrategy = await resolveFileExists(
this.createModal,
this.hideModal,
{ name: rootFolder, isFolder: true },
1,
this.$gettext,
this.$gettextInterpolate,
true
),
status: 'danger'
})
return
)
resolveStrategies[rootFolder] = resolveStrategy
}
const resolveStrategy = resolveStrategies[rootFolder]
if (resolveStrategy.strategy === ResolveStrategy.SKIP) {
continue
}
if (resolveStrategy.strategy === ResolveStrategy.KEEP_BOTH) {
const newFolderName = resolveFileNameDuplicate(rootFolder, '', this.files)

file.meta.relativeFolder = file.meta.relativeFolder.replace(
`/${rootFolder}`,
`/${newFolderName}`
)
file.meta.relativePath = file.meta.relativePath.replace(
`/${rootFolder}/`,
`/${newFolderName}/`
)
file.meta.tusEndpoint = file.meta.tusEndpoint.replace(
`/${rootFolder}`,
`/${newFolderName}`
)
const data = file.data as any
data.relativePath = data.relativePath.replace(`/${rootFolder}/`, `/${newFolderName}/`)
file.meta.routeItem = `/${newFolderName}`
}
}

// Folder does not exist, no need to care about files inside -> continue
continue
}
// Logic for files
const exists = this.files.find((f) => f.name === file.name)
if (exists) {
conflicts.push(file)
}
}

if (conflicts.length) {
this.displayOverwriteDialog(uppyResources, conflicts)
await this.displayOverwriteDialog(uppyResources, conflicts)
} else {
this.handleUppyFileUpload(uppyResources)
}
Expand Down Expand Up @@ -792,56 +822,48 @@ export default defineComponent({

async handleUppyFileUpload(files: UppyResource[]) {
this.$uppyService.publish('uploadStarted')
await this.createDirectoryTree(files)
await this.createDirectoryTree(files, this.files)
this.$uppyService.publish('addedForUpload', files)
this.$uppyService.uploadFiles(files)
},

displayOverwriteDialog(files: UppyResource[], conflicts) {
const title = this.$ngettext(
'File already exists',
'Some files already exist',
conflicts.length
)

const isVersioningEnabled = this.isUserContext && this.capabilities?.files?.versioning

let translatedMsg
if (isVersioningEnabled) {
translatedMsg = this.$ngettext(
'The following resource already exists: %{resources}. Do you want to create a new version for it?',
'The following resources already exist: %{resources}. Do you want to create a new version for them?',
conflicts.length
)
} else {
translatedMsg = this.$ngettext(
'The following resource already exists: %{resources}. Do you want to overwrite it?',
'The following resources already exist: %{resources}. Do you want to overwrite them?',
conflicts.length
async displayOverwriteDialog(files: UppyResource[], conflicts) {
let count = 0
for (const file of files) {
const resolveConflict: ResolveConflict = await resolveFileExists(
this.createModal,
this.hideModal,
{ name: file.name, isFolder: false },
files.length - count,
this.$gettext,
this.$gettextInterpolate,
false
)
}
const message = this.$gettextInterpolate(translatedMsg, {
resources: conflicts.map((f) => `${f.name}`).join(', ')
})

const modal = {
variation: isVersioningEnabled ? 'passive' : 'danger',
icon: 'upload-cloud',
title,
message,
cancelText: this.$gettext('Cancel'),
confirmText: isVersioningEnabled ? this.$gettext('Create') : this.$gettext('Overwrite'),
onCancel: () => {
this.$uppyService.clearInputs()
this.hideModal()
},
onConfirm: () => {
this.hideModal()
count += 1
lookacat marked this conversation as resolved.
Show resolved Hide resolved
if (resolveConflict.doForAllConflicts) {
if (resolveConflict.strategy === ResolveStrategy.SKIP) {
return
}
if (resolveConflict.strategy === ResolveStrategy.KEEP_BOTH) {
for (const f of files) {
const ext = extractExtensionFromFile({ name: f.name } as Resource)
f.name = resolveFileNameDuplicate(f.name, ext, this.files)
}
}
// strategy replace doesn't need a case here
this.handleUppyFileUpload(files)
return
}
if (resolveConflict.strategy === ResolveStrategy.SKIP) {
continue
}
if (resolveConflict.strategy === ResolveStrategy.KEEP_BOTH) {
const ext = extractExtensionFromFile({ name: file.name } as Resource)
file.name = resolveFileNameDuplicate(file.name, ext, this.files)
}
// strategy replace doesn't need a case here
this.handleUppyFileUpload([file])
}

this.createModal(modal)
}
}
})
Expand Down
4 changes: 2 additions & 2 deletions packages/web-app-files/src/helpers/resource/copyMove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { join } from 'path'
import { buildResource } from '../resources'
import { DavProperties } from 'web-pkg/src/constants'

enum ResolveStrategy {
export enum ResolveStrategy {
SKIP,
REPLACE,
KEEP_BOTH
Expand All @@ -18,7 +18,7 @@ interface FileConflict {
strategy?: ResolveStrategy
}

const resolveFileExists = (
export const resolveFileExists = (
createModal,
hideModal,
resource,
Expand Down
9 changes: 6 additions & 3 deletions packages/web-runtime/src/composables/upload/useUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { computed, Ref, unref, watch } from '@vue/composition-api'
import { UppyService } from '../../services/uppyService'
import * as uuid from 'uuid'
import { Resource } from 'web-client/src/helpers/resource'

export interface UppyResource {
id?: string
Expand Down Expand Up @@ -43,7 +44,7 @@ interface UploadOptions {
}

interface UploadResult {
createDirectoryTree(files: UppyResource[]): void
createDirectoryTree(files: UppyResource[], existingFiles: Resource[]): void
}

export function useUpload(options: UploadOptions): UploadResult {
Expand Down Expand Up @@ -121,7 +122,7 @@ const createDirectoryTree = ({
publicLinkPassword?: Ref<string>
uppyService: UppyService
}) => {
return async (files: UppyResource[]) => {
return async (files: UppyResource[], existingFiles: Resource[]) => {
const { owncloudSdk: client } = clientService
const createdFolders = []
for (const file of files) {
Expand Down Expand Up @@ -181,7 +182,9 @@ const createDirectoryTree = ({
unref(publicLinkPassword)
)
} else {
await client.files.createFolder(`${file.meta.webDavBasePath}/${folderToCreate}`)
if (!existingFiles.some((f) => f.path === folderToCreate)) {
await client.files.createFolder(`${file.meta.webDavBasePath}/${folderToCreate}`)
}
}

uppyService.publish('uploadSuccess', uppyResource)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('useUpload', () => {
}
]

await wrapper.vm.createDirectoryTree(uppyResources)
await wrapper.vm.createDirectoryTree(uppyResources, [])
expect(createFolderMock).toHaveBeenCalledTimes(4)
})
})