mirror of
https://github.com/filebrowser/filebrowser.git
synced 2024-06-07 23:00:43 +00:00
feat: file copy, move and paste conflict checking
This commit is contained in:
parent
9a2ebbabe2
commit
eed9da1471
@ -2,6 +2,7 @@ package fileutils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
@ -25,7 +26,7 @@ func CopyFile(fs afero.Fs, source, dest string) error {
|
||||
}
|
||||
|
||||
// Create the destination file.
|
||||
dst, err := fs.Create(dest)
|
||||
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -112,25 +112,25 @@ export async function post (url, content = '', overwrite = false, onupload) {
|
||||
})
|
||||
}
|
||||
|
||||
function moveCopy (items, copy = false) {
|
||||
function moveCopy (items, copy = false, overwrite = false) {
|
||||
let promises = []
|
||||
|
||||
for (let item of items) {
|
||||
const from = removePrefix(item.from)
|
||||
const to = encodeURIComponent(removePrefix(item.to))
|
||||
const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}`
|
||||
const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}&override=${overwrite}`
|
||||
promises.push(resourceAction(url, 'PATCH'))
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
export function move (items) {
|
||||
return moveCopy(items)
|
||||
export function move (items, overwrite = false) {
|
||||
return moveCopy(items, false, overwrite)
|
||||
}
|
||||
|
||||
export function copy (items) {
|
||||
return moveCopy(items, true)
|
||||
export function copy (items, overwrite = false) {
|
||||
return moveCopy(items, true, overwrite)
|
||||
}
|
||||
|
||||
export async function checksum (url, algo) {
|
||||
|
@ -261,23 +261,43 @@ export default {
|
||||
for (let item of this.$store.state.clipboard.items) {
|
||||
const from = item.from.endsWith('/') ? item.from.slice(0, -1) : item.from
|
||||
const to = this.$route.path + item.name
|
||||
items.push({ from, to })
|
||||
items.push({ from, to, name: item.name })
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.$store.state.clipboard.key === 'x') {
|
||||
api.move(items).then(() => {
|
||||
let action = (overwrite) => {
|
||||
api.copy(items, overwrite).then(() => {
|
||||
this.$store.commit('setReload', true)
|
||||
}).catch(this.$showError)
|
||||
}
|
||||
|
||||
if (this.$store.state.clipboard.key === 'x') {
|
||||
action = (overwrite) => {
|
||||
api.move(items, overwrite).then(() => {
|
||||
this.$store.commit('setReload', true)
|
||||
}).catch(this.$showError)
|
||||
}
|
||||
}
|
||||
|
||||
let conflict = upload.checkConflict(items, this.req.items)
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'replace',
|
||||
confirm: (event) => {
|
||||
event.preventDefault()
|
||||
this.$store.commit('closeHovers')
|
||||
action(true)
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
api.copy(items).then(() => {
|
||||
this.$store.commit('setReload', true)
|
||||
}).catch(this.$showError)
|
||||
action(false)
|
||||
},
|
||||
resizeEvent () {
|
||||
// Update the columns size based on the window width.
|
||||
|
@ -36,6 +36,7 @@ import { mapMutations, mapGetters, mapState } from 'vuex'
|
||||
import filesize from 'filesize'
|
||||
import moment from 'moment'
|
||||
import { files as api } from '@/api'
|
||||
import * as upload from '@/utils/upload'
|
||||
|
||||
export default {
|
||||
name: 'item',
|
||||
@ -110,26 +111,55 @@ export default {
|
||||
|
||||
el.style.opacity = 1
|
||||
},
|
||||
drop: function (event) {
|
||||
drop: async function (event) {
|
||||
if (!this.canDrop) return
|
||||
event.preventDefault()
|
||||
|
||||
if (this.selectedCount === 0) return
|
||||
|
||||
let el = event.target
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (el !== null && !el.classList.contains('item')) {
|
||||
el = el.parentElement
|
||||
}
|
||||
}
|
||||
|
||||
let items = []
|
||||
|
||||
for (let i of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[i].url,
|
||||
to: this.url + this.req.items[i].name
|
||||
to: this.url + this.req.items[i].name,
|
||||
name: this.req.items[i].name
|
||||
})
|
||||
}
|
||||
|
||||
let base = el.querySelector('.name').innerHTML + '/'
|
||||
let path = this.$route.path + base
|
||||
let baseItems = (await api.fetch(path)).items
|
||||
|
||||
let action = (overwrite) => {
|
||||
api.move(items, overwrite).then(() => {
|
||||
this.$store.commit('setReload', true)
|
||||
}).catch(this.$showError)
|
||||
}
|
||||
|
||||
api.move(items)
|
||||
.then(() => {
|
||||
this.$store.commit('setReload', true)
|
||||
let conflict = upload.checkConflict(items, baseItems)
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'replace',
|
||||
confirm: (event) => {
|
||||
event.preventDefault()
|
||||
this.$store.commit('closeHovers')
|
||||
action(true)
|
||||
}
|
||||
})
|
||||
.catch(this.$showError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
action(false)
|
||||
},
|
||||
click: function (event) {
|
||||
if (this.selectedCount !== 0) event.preventDefault()
|
||||
|
@ -28,6 +28,7 @@ import { mapState } from 'vuex'
|
||||
import FileList from './FileList'
|
||||
import { files as api } from '@/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
import * as upload from '@/utils/upload'
|
||||
|
||||
export default {
|
||||
name: 'copy',
|
||||
@ -42,25 +43,46 @@ export default {
|
||||
methods: {
|
||||
copy: async function (event) {
|
||||
event.preventDefault()
|
||||
buttons.loading('copy')
|
||||
let items = []
|
||||
|
||||
// Create a new promise for each file.
|
||||
for (let item of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name)
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
||||
name: this.req.items[item].name
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await api.copy(items)
|
||||
buttons.success('copy')
|
||||
this.$router.push({ path: this.dest })
|
||||
} catch (e) {
|
||||
buttons.done('copy')
|
||||
this.$showError(e)
|
||||
let action = async (overwrite) => {
|
||||
buttons.loading('copy')
|
||||
|
||||
await api.copy(items, overwrite).then(() => {
|
||||
buttons.success('copy')
|
||||
this.$router.push({ path: this.dest })
|
||||
}).catch((e) => {
|
||||
buttons.done('copy')
|
||||
this.$showError(e)
|
||||
})
|
||||
}
|
||||
|
||||
let dstItems = (await api.fetch(this.dest)).items
|
||||
let conflict = upload.checkConflict(items, dstItems)
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'replace',
|
||||
confirm: (event) => {
|
||||
event.preventDefault()
|
||||
this.$store.commit('closeHovers')
|
||||
action(true)
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
action(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,19 +41,7 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// If we're showing this on a listing,
|
||||
// we can use the current request object
|
||||
// to fill the move options.
|
||||
if (this.req.kind === 'listing') {
|
||||
this.fillOptions(this.req)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we must be on a preview or editor
|
||||
// so we fetch the data from the previous directory.
|
||||
files.fetch(url.removeLastDir(this.$route.path))
|
||||
.then(this.fillOptions)
|
||||
.catch(this.$showError)
|
||||
this.fillOptions(this.req)
|
||||
},
|
||||
methods: {
|
||||
fillOptions (req) {
|
||||
|
@ -27,6 +27,7 @@ import { mapState } from 'vuex'
|
||||
import FileList from './FileList'
|
||||
import { files as api } from '@/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
import * as upload from '@/utils/upload'
|
||||
|
||||
export default {
|
||||
name: 'move',
|
||||
@ -41,26 +42,45 @@ export default {
|
||||
methods: {
|
||||
move: async function (event) {
|
||||
event.preventDefault()
|
||||
buttons.loading('move')
|
||||
let items = []
|
||||
|
||||
for (let item of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name)
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
||||
name: this.req.items[item].name
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
api.move(items)
|
||||
buttons.success('move')
|
||||
this.$router.push({ path: this.dest })
|
||||
} catch (e) {
|
||||
buttons.done('move')
|
||||
this.$showError(e)
|
||||
let action = async (overwrite) => {
|
||||
buttons.loading('move')
|
||||
|
||||
await api.move(items, overwrite).then(() => {
|
||||
buttons.success('move')
|
||||
this.$router.push({ path: this.dest })
|
||||
}).catch((e) => {
|
||||
buttons.done('move')
|
||||
this.$showError(e)
|
||||
})
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
let dstItems = (await api.fetch(this.dest)).items
|
||||
let conflict = upload.checkConflict(items, dstItems)
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit('showHover', {
|
||||
prompt: 'replace',
|
||||
confirm: (event) => {
|
||||
event.preventDefault()
|
||||
this.$store.commit('closeHovers')
|
||||
action(true)
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
action(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +148,12 @@ var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request,
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
if r.URL.Query().Get("override") != "true" {
|
||||
if _, err := d.user.Fs.Stat(dst); err == nil {
|
||||
return http.StatusConflict, nil
|
||||
}
|
||||
}
|
||||
|
||||
err = d.RunHook(func() error {
|
||||
switch action {
|
||||
// TODO: use enum
|
||||
|
Loading…
Reference in New Issue
Block a user