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 (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
@ -25,7 +26,7 @@ func CopyFile(fs afero.Fs, source, dest string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the destination file.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
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 = []
|
let promises = []
|
||||||
|
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
const from = removePrefix(item.from)
|
const from = removePrefix(item.from)
|
||||||
const to = encodeURIComponent(removePrefix(item.to))
|
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'))
|
promises.push(resourceAction(url, 'PATCH'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function move (items) {
|
export function move (items, overwrite = false) {
|
||||||
return moveCopy(items)
|
return moveCopy(items, false, overwrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function copy (items) {
|
export function copy (items, overwrite = false) {
|
||||||
return moveCopy(items, true)
|
return moveCopy(items, true, overwrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checksum (url, algo) {
|
export async function checksum (url, algo) {
|
||||||
|
@ -261,23 +261,43 @@ export default {
|
|||||||
for (let item of this.$store.state.clipboard.items) {
|
for (let item of this.$store.state.clipboard.items) {
|
||||||
const from = item.from.endsWith('/') ? item.from.slice(0, -1) : item.from
|
const from = item.from.endsWith('/') ? item.from.slice(0, -1) : item.from
|
||||||
const to = this.$route.path + item.name
|
const to = this.$route.path + item.name
|
||||||
items.push({ from, to })
|
items.push({ from, to, name: item.name })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.$store.state.clipboard.key === 'x') {
|
let action = (overwrite) => {
|
||||||
api.move(items).then(() => {
|
api.copy(items, overwrite).then(() => {
|
||||||
this.$store.commit('setReload', true)
|
this.$store.commit('setReload', true)
|
||||||
}).catch(this.$showError)
|
}).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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
api.copy(items).then(() => {
|
action(false)
|
||||||
this.$store.commit('setReload', true)
|
|
||||||
}).catch(this.$showError)
|
|
||||||
},
|
},
|
||||||
resizeEvent () {
|
resizeEvent () {
|
||||||
// Update the columns size based on the window width.
|
// Update the columns size based on the window width.
|
||||||
|
@ -36,6 +36,7 @@ import { mapMutations, mapGetters, mapState } from 'vuex'
|
|||||||
import filesize from 'filesize'
|
import filesize from 'filesize'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { files as api } from '@/api'
|
import { files as api } from '@/api'
|
||||||
|
import * as upload from '@/utils/upload'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'item',
|
name: 'item',
|
||||||
@ -110,26 +111,55 @@ export default {
|
|||||||
|
|
||||||
el.style.opacity = 1
|
el.style.opacity = 1
|
||||||
},
|
},
|
||||||
drop: function (event) {
|
drop: async function (event) {
|
||||||
if (!this.canDrop) return
|
if (!this.canDrop) return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
if (this.selectedCount === 0) return
|
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 = []
|
let items = []
|
||||||
|
|
||||||
for (let i of this.selected) {
|
for (let i of this.selected) {
|
||||||
items.push({
|
items.push({
|
||||||
from: this.req.items[i].url,
|
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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
api.move(items)
|
let base = el.querySelector('.name').innerHTML + '/'
|
||||||
.then(() => {
|
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)
|
this.$store.commit('setReload', true)
|
||||||
|
}).catch(this.$showError)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
click: function (event) {
|
||||||
if (this.selectedCount !== 0) event.preventDefault()
|
if (this.selectedCount !== 0) event.preventDefault()
|
||||||
|
@ -28,6 +28,7 @@ import { mapState } from 'vuex'
|
|||||||
import FileList from './FileList'
|
import FileList from './FileList'
|
||||||
import { files as api } from '@/api'
|
import { files as api } from '@/api'
|
||||||
import buttons from '@/utils/buttons'
|
import buttons from '@/utils/buttons'
|
||||||
|
import * as upload from '@/utils/upload'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'copy',
|
name: 'copy',
|
||||||
@ -42,25 +43,46 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
copy: async function (event) {
|
copy: async function (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
buttons.loading('copy')
|
|
||||||
let items = []
|
let items = []
|
||||||
|
|
||||||
// Create a new promise for each file.
|
// Create a new promise for each file.
|
||||||
for (let item of this.selected) {
|
for (let item of this.selected) {
|
||||||
items.push({
|
items.push({
|
||||||
from: this.req.items[item].url,
|
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 {
|
let action = async (overwrite) => {
|
||||||
await api.copy(items)
|
buttons.loading('copy')
|
||||||
|
|
||||||
|
await api.copy(items, overwrite).then(() => {
|
||||||
buttons.success('copy')
|
buttons.success('copy')
|
||||||
this.$router.push({ path: this.dest })
|
this.$router.push({ path: this.dest })
|
||||||
} catch (e) {
|
}).catch((e) => {
|
||||||
buttons.done('copy')
|
buttons.done('copy')
|
||||||
this.$showError(e)
|
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 () {
|
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)
|
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)
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fillOptions (req) {
|
fillOptions (req) {
|
||||||
|
@ -27,6 +27,7 @@ import { mapState } from 'vuex'
|
|||||||
import FileList from './FileList'
|
import FileList from './FileList'
|
||||||
import { files as api } from '@/api'
|
import { files as api } from '@/api'
|
||||||
import buttons from '@/utils/buttons'
|
import buttons from '@/utils/buttons'
|
||||||
|
import * as upload from '@/utils/upload'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'move',
|
name: 'move',
|
||||||
@ -41,26 +42,45 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
move: async function (event) {
|
move: async function (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
buttons.loading('move')
|
|
||||||
let items = []
|
let items = []
|
||||||
|
|
||||||
for (let item of this.selected) {
|
for (let item of this.selected) {
|
||||||
items.push({
|
items.push({
|
||||||
from: this.req.items[item].url,
|
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 {
|
let action = async (overwrite) => {
|
||||||
api.move(items)
|
buttons.loading('move')
|
||||||
|
|
||||||
|
await api.move(items, overwrite).then(() => {
|
||||||
buttons.success('move')
|
buttons.success('move')
|
||||||
this.$router.push({ path: this.dest })
|
this.$router.push({ path: this.dest })
|
||||||
} catch (e) {
|
}).catch((e) => {
|
||||||
buttons.done('move')
|
buttons.done('move')
|
||||||
this.$showError(e)
|
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()
|
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
|
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 {
|
err = d.RunHook(func() error {
|
||||||
switch action {
|
switch action {
|
||||||
// TODO: use enum
|
// TODO: use enum
|
||||||
|
Loading…
Reference in New Issue
Block a user