Sharing: Refactor API and entities #225
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
a836dd1497
commit
15d32016c6
|
@ -80,8 +80,8 @@
|
|||
:search-input.sync="search"
|
||||
:items="pathItems"
|
||||
:loading="loading"
|
||||
item-text="text"
|
||||
item-value="value"
|
||||
item-text="abs"
|
||||
item-value="abs"
|
||||
:label="label.SharePath"
|
||||
:disabled="!model.AccShare || loading"
|
||||
>
|
||||
|
@ -145,8 +145,8 @@
|
|||
:search-input.sync="search"
|
||||
:items="pathItems"
|
||||
:loading="loading"
|
||||
item-text="text"
|
||||
item-value="value"
|
||||
item-text="abs"
|
||||
item-value="abs"
|
||||
:label="label.SyncPath"
|
||||
:disabled="!model.AccSync || loading"
|
||||
>
|
||||
|
@ -317,7 +317,7 @@
|
|||
search: null,
|
||||
path: "/",
|
||||
paths: [
|
||||
{"text": "/", "value": "/"}
|
||||
{"abs": "/"}
|
||||
],
|
||||
pathItems: [],
|
||||
newPath: "",
|
||||
|
@ -390,8 +390,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$emit('cancel');
|
||||
|
@ -437,12 +436,12 @@
|
|||
return result;
|
||||
},
|
||||
onChange() {
|
||||
this.paths = [{"text": "/", "value": "/"}];
|
||||
this.paths = [{"abs": "/"}];
|
||||
|
||||
this.loading = true;
|
||||
this.model.Ls().then(l => {
|
||||
for (let i = 0; i < l.length; i++) {
|
||||
this.paths.push({"text": l[i], "value": l[i]});
|
||||
this.model.Dirs().then(p => {
|
||||
for (let i = 0; i < p.length; i++) {
|
||||
this.paths.push(p[i]);
|
||||
}
|
||||
|
||||
this.pathItems = [...this.paths];
|
||||
|
@ -461,11 +460,11 @@
|
|||
this.newPath = "";
|
||||
} else {
|
||||
this.newPath = q;
|
||||
this.pathItems = this.paths.concat([{"text": q, "value": q}]);
|
||||
this.pathItems = this.paths.concat([{"abs": q}]);
|
||||
}
|
||||
},
|
||||
show: function (show) {
|
||||
if(show) {
|
||||
if (show) {
|
||||
this.onChange();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
:items="pathItems"
|
||||
:loading="loading"
|
||||
:disabled="loading || noAccounts"
|
||||
item-text="text"
|
||||
item-value="value"
|
||||
item-text="abs"
|
||||
item-value="abs"
|
||||
:label="labels.path"
|
||||
>
|
||||
</v-autocomplete>
|
||||
|
@ -74,7 +74,7 @@
|
|||
accounts: [],
|
||||
path: "/",
|
||||
paths: [
|
||||
{"text": "/", "value": "/"}
|
||||
{"abs": "/"}
|
||||
],
|
||||
pathItems: [],
|
||||
newPath: "",
|
||||
|
@ -104,7 +104,7 @@
|
|||
(files) => {
|
||||
this.loading = false;
|
||||
|
||||
if(files.length === 1) {
|
||||
if (files.length === 1) {
|
||||
this.$notify.success("One photo shared");
|
||||
} else {
|
||||
this.$notify.success(files.length + " photos shared");
|
||||
|
@ -115,13 +115,12 @@
|
|||
).catch(() => this.loading = false);
|
||||
},
|
||||
onChange() {
|
||||
this.paths = [{"text": "/", "value": "/"}];
|
||||
this.paths = [{"abs": "/"}];
|
||||
|
||||
this.loading = true;
|
||||
this.account.Ls().then(l => {
|
||||
console.log("result", l);
|
||||
for (let i = 0; i < l.length; i++) {
|
||||
this.paths.push({"text": l[i], "value": l[i]});
|
||||
this.account.Dirs().then(p => {
|
||||
for (let i = 0; i < p.length; i++) {
|
||||
this.paths.push(p[i]);
|
||||
}
|
||||
|
||||
this.pathItems = [...this.paths];
|
||||
|
@ -160,7 +159,7 @@
|
|||
this.newPath = "";
|
||||
} else {
|
||||
this.newPath = q;
|
||||
this.pathItems = this.paths.concat([{"text": q, "value": q}]);
|
||||
this.pathItems = this.paths.concat([{"abs": q}]);
|
||||
}
|
||||
},
|
||||
show: function (show) {
|
||||
|
|
|
@ -46,20 +46,8 @@ class Account extends Abstract {
|
|||
return this.ID;
|
||||
}
|
||||
|
||||
toggleShare() {
|
||||
const values = { AccShare: !this.AccShare };
|
||||
|
||||
return Api.put(this.getEntityResource(), values).then((response) => Promise.resolve(this.setValues(response.data)));
|
||||
}
|
||||
|
||||
toggleSync() {
|
||||
const values = { AccSync: !this.AccSync };
|
||||
|
||||
return Api.put(this.getEntityResource(), values).then((response) => Promise.resolve(this.setValues(response.data)));
|
||||
}
|
||||
|
||||
Ls() {
|
||||
return Api.get(this.getEntityResource() + "/ls").then((response) => Promise.resolve(response.data));
|
||||
Dirs() {
|
||||
return Api.get(this.getEntityResource() + "/dirs").then((response) => Promise.resolve(response.data));
|
||||
}
|
||||
|
||||
Share(UUIDs, dest) {
|
||||
|
|
|
@ -69,12 +69,12 @@ func GetAccount(router *gin.RouterGroup, conf *config.Config) {
|
|||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/accounts/:id/ls
|
||||
// GET /api/v1/accounts/:id/dirs
|
||||
//
|
||||
// Parameters:
|
||||
// id: string Account ID as returned by the API
|
||||
func LsAccount(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/accounts/:id/ls", func(c *gin.Context) {
|
||||
func GetAccountDirs(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.GET("/accounts/:id/dirs", func(c *gin.Context) {
|
||||
if Unauthorized(c, conf) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
|
||||
return
|
||||
|
@ -90,7 +90,7 @@ func LsAccount(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
list, err := m.Ls()
|
||||
list, err := m.Directories()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("account: %s", err.Error())
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/service/webdav"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
|
@ -83,14 +84,14 @@ func (m *Account) Delete(db *gorm.DB) error {
|
|||
return db.Delete(m).Error
|
||||
}
|
||||
|
||||
// Ls returns a list of directories or albums in an account.
|
||||
func (m *Account) Ls() (result []string, err error) {
|
||||
// Directories returns a list of directories or albums in an account.
|
||||
func (m *Account) Directories() (result fs.FileInfos, err error) {
|
||||
if m.AccType == string(service.TypeWebDAV) {
|
||||
c := webdav.Connect(m.AccURL, m.AccUser, m.AccPass)
|
||||
result, err = c.Directories("/", true)
|
||||
}
|
||||
|
||||
sort.Strings(result)
|
||||
sort.Sort(result)
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ type FileSync struct {
|
|||
FileID uint `gorm:"index;"`
|
||||
AccountID uint `gorm:"primary_key;auto_increment:false"`
|
||||
RemoteName string `gorm:"type:varbinary(256);primary_key;auto_increment:false"`
|
||||
RemoteDate time.Time
|
||||
RemoteSize int64
|
||||
Status string `gorm:"type:varbinary(16);"`
|
||||
Error string `gorm:"type:varbinary(512);"`
|
||||
File *File
|
||||
|
@ -32,6 +34,7 @@ func NewFileSync(accountID uint, remoteName string) *FileSync {
|
|||
result := &FileSync{
|
||||
AccountID: accountID,
|
||||
RemoteName: remoteName,
|
||||
Status: "new",
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
|
@ -72,7 +72,7 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||
|
||||
api.GetAccounts(v1, conf)
|
||||
api.GetAccount(v1, conf)
|
||||
api.LsAccount(v1, conf)
|
||||
api.GetAccountDirs(v1, conf)
|
||||
api.ShareWithAccount(v1, conf)
|
||||
api.CreateAccount(v1, conf)
|
||||
api.DeleteAccount(v1, conf)
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"path"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/studio-b12/gowebdav"
|
||||
)
|
||||
|
||||
|
@ -41,7 +42,7 @@ func (c Client) readDir(path string) ([]os.FileInfo, error) {
|
|||
}
|
||||
|
||||
// Files returns all files in path as string slice.
|
||||
func (c Client) Files(dir string) (result []string, err error) {
|
||||
func (c Client) Files(dir string) (result fs.FileInfos, err error) {
|
||||
files, err := c.readDir(dir)
|
||||
|
||||
if err != nil {
|
||||
|
@ -52,14 +53,17 @@ func (c Client) Files(dir string) (result []string, err error) {
|
|||
if !file.Mode().IsRegular() {
|
||||
continue
|
||||
}
|
||||
result = append(result, fmt.Sprintf("%s/%s", dir, file.Name()))
|
||||
|
||||
info := fs.NewFileInfo(file, dir)
|
||||
|
||||
result = append(result, info)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Directories returns all sub directories in path as string slice.
|
||||
func (c Client) Directories(root string, recursive bool) (result []string, err error) {
|
||||
func (c Client) Directories(root string, recursive bool) (result fs.FileInfos, err error) {
|
||||
files, err := c.readDir(root)
|
||||
|
||||
if err != nil {
|
||||
|
@ -75,12 +79,12 @@ func (c Client) Directories(root string, recursive bool) (result []string, err e
|
|||
continue
|
||||
}
|
||||
|
||||
dir := fmt.Sprintf("%s/%s", root, file.Name())
|
||||
info := fs.NewFileInfo(file, root)
|
||||
|
||||
result = append(result, dir)
|
||||
result = append(result, info)
|
||||
|
||||
if recursive {
|
||||
subDirs, err := c.Directories(dir, true)
|
||||
subDirs, err := c.Directories(info.Abs, true)
|
||||
|
||||
if err != nil {
|
||||
return result, err
|
||||
|
@ -125,7 +129,7 @@ func (c Client) DownloadDir(from, to string, recursive bool) (errs []error) {
|
|||
}
|
||||
|
||||
for _, file := range files {
|
||||
dest := to + string(os.PathSeparator) + file
|
||||
dest := to + string(os.PathSeparator) + file.Abs
|
||||
|
||||
if _, err := os.Stat(dest); err == nil {
|
||||
// File exists
|
||||
|
@ -135,7 +139,7 @@ func (c Client) DownloadDir(from, to string, recursive bool) (errs []error) {
|
|||
continue
|
||||
}
|
||||
|
||||
if err := c.Download(file, dest); err != nil {
|
||||
if err := c.Download(file.Abs, dest); err != nil {
|
||||
msg := fmt.Errorf("webdav: %s", err)
|
||||
errs = append(errs, msg)
|
||||
log.Error(msg)
|
||||
|
@ -150,7 +154,7 @@ func (c Client) DownloadDir(from, to string, recursive bool) (errs []error) {
|
|||
dirs, err := c.Directories(from, false)
|
||||
|
||||
for _, dir := range dirs {
|
||||
errs = append(errs, c.DownloadDir(dir, to, true)...)
|
||||
errs = append(errs, c.DownloadDir(dir.Abs, to, true)...)
|
||||
}
|
||||
|
||||
return errs
|
||||
|
|
|
@ -53,7 +53,11 @@ func TestClient_Directories(t *testing.T) {
|
|||
t.Fatal("no directories found")
|
||||
}
|
||||
|
||||
assert.Equal(t, "/Photos", dirs[0])
|
||||
assert.IsType(t, fs.FileInfo{}, dirs[0])
|
||||
assert.Equal(t, "Photos", dirs[0].Name)
|
||||
assert.Equal(t, "/Photos", dirs[0].Abs)
|
||||
assert.Equal(t, true, dirs[0].Dir)
|
||||
assert.Equal(t, int64(0), dirs[0].Size)
|
||||
})
|
||||
|
||||
t.Run("recursive", func(t *testing.T) {
|
||||
|
@ -87,7 +91,7 @@ func TestClient_Download(t *testing.T) {
|
|||
t.Fatal("no files to download")
|
||||
}
|
||||
|
||||
if err := c.Download(files[0], tempFile); err != nil {
|
||||
if err := c.Download(files[0].Abs, tempFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
58
pkg/fs/fileinfo.go
Normal file
58
pkg/fs/fileinfo.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileInfo struct {
|
||||
Name string `json:"name"`
|
||||
Abs string `json:"abs"`
|
||||
Size int64 `json:"size"`
|
||||
Date time.Time `json:"date"`
|
||||
Dir bool `json:"dir"`
|
||||
}
|
||||
|
||||
func NewFileInfo(info os.FileInfo, dir string) FileInfo {
|
||||
if dir == "/" {
|
||||
dir = ""
|
||||
} else if len(dir) > 0 {
|
||||
if dir[len(dir)-1:] == "/" {
|
||||
dir = dir[:len(dir)-1]
|
||||
}
|
||||
|
||||
if dir[0:1] != "/" {
|
||||
dir = "/" + dir
|
||||
}
|
||||
}
|
||||
|
||||
result := FileInfo{
|
||||
Name: info.Name(),
|
||||
Abs: fmt.Sprintf("%s/%s", dir, info.Name()),
|
||||
Size: info.Size(),
|
||||
Date: info.ModTime(),
|
||||
Dir: info.IsDir(),
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type FileInfos []FileInfo
|
||||
|
||||
func (infos FileInfos) Len() int { return len(infos) }
|
||||
func (infos FileInfos) Swap(i, j int) { infos[i], infos[j] = infos[j], infos[i] }
|
||||
func (infos FileInfos) Less(i, j int) bool {
|
||||
return strings.Compare(infos[i].Abs, infos[j].Abs) == -1
|
||||
}
|
||||
|
||||
func NewFileInfos(infos []os.FileInfo, dir string) FileInfos {
|
||||
var result FileInfos
|
||||
|
||||
for _, info := range infos {
|
||||
result = append(result, NewFileInfo(info, dir))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
46
pkg/fs/fileinfo_test.go
Normal file
46
pkg/fs/fileinfo_test.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewFileInfo(t *testing.T) {
|
||||
info, err := os.Stat("testdata/test.jpg")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result := NewFileInfo(info, "Photos/")
|
||||
|
||||
assert.Equal(t, "test.jpg", result.Name)
|
||||
assert.Equal(t, "/Photos/test.jpg", result.Abs)
|
||||
assert.Equal(t, int64(10990), result.Size)
|
||||
assert.IsType(t, time.Time{}, result.Date)
|
||||
assert.Equal(t, false, result.Dir)
|
||||
}
|
||||
|
||||
func TestNewFileInfos(t *testing.T) {
|
||||
infos, err := ioutil.ReadDir("testdata")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result := NewFileInfos(infos, "/")
|
||||
|
||||
if len(result) < 1 {
|
||||
t.Fatal("empty result")
|
||||
}
|
||||
|
||||
assert.Equal(t, "test.jpg", result[0].Name)
|
||||
assert.Equal(t, "/test.jpg", result[0].Abs)
|
||||
assert.Equal(t, int64(10990), result[0].Size)
|
||||
assert.IsType(t, time.Time{}, result[0].Date)
|
||||
assert.Equal(t, false, result[0].Dir)
|
||||
}
|
Loading…
Reference in a new issue