Sharing: Refactor API and entities #225

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-04-02 18:17:07 +02:00
parent a836dd1497
commit 15d32016c6
11 changed files with 158 additions and 56 deletions

View file

@ -80,8 +80,8 @@
:search-input.sync="search" :search-input.sync="search"
:items="pathItems" :items="pathItems"
:loading="loading" :loading="loading"
item-text="text" item-text="abs"
item-value="value" item-value="abs"
:label="label.SharePath" :label="label.SharePath"
:disabled="!model.AccShare || loading" :disabled="!model.AccShare || loading"
> >
@ -145,8 +145,8 @@
:search-input.sync="search" :search-input.sync="search"
:items="pathItems" :items="pathItems"
:loading="loading" :loading="loading"
item-text="text" item-text="abs"
item-value="value" item-value="abs"
:label="label.SyncPath" :label="label.SyncPath"
:disabled="!model.AccSync || loading" :disabled="!model.AccSync || loading"
> >
@ -317,7 +317,7 @@
search: null, search: null,
path: "/", path: "/",
paths: [ paths: [
{"text": "/", "value": "/"} {"abs": "/"}
], ],
pathItems: [], pathItems: [],
newPath: "", newPath: "",
@ -390,8 +390,7 @@
} }
} }
}, },
computed: { computed: {},
},
methods: { methods: {
cancel() { cancel() {
this.$emit('cancel'); this.$emit('cancel');
@ -437,12 +436,12 @@
return result; return result;
}, },
onChange() { onChange() {
this.paths = [{"text": "/", "value": "/"}]; this.paths = [{"abs": "/"}];
this.loading = true; this.loading = true;
this.model.Ls().then(l => { this.model.Dirs().then(p => {
for (let i = 0; i < l.length; i++) { for (let i = 0; i < p.length; i++) {
this.paths.push({"text": l[i], "value": l[i]}); this.paths.push(p[i]);
} }
this.pathItems = [...this.paths]; this.pathItems = [...this.paths];
@ -461,11 +460,11 @@
this.newPath = ""; this.newPath = "";
} else { } else {
this.newPath = q; this.newPath = q;
this.pathItems = this.paths.concat([{"text": q, "value": q}]); this.pathItems = this.paths.concat([{"abs": q}]);
} }
}, },
show: function (show) { show: function (show) {
if(show) { if (show) {
this.onChange(); this.onChange();
} }
} }

View file

@ -32,8 +32,8 @@
:items="pathItems" :items="pathItems"
:loading="loading" :loading="loading"
:disabled="loading || noAccounts" :disabled="loading || noAccounts"
item-text="text" item-text="abs"
item-value="value" item-value="abs"
:label="labels.path" :label="labels.path"
> >
</v-autocomplete> </v-autocomplete>
@ -74,7 +74,7 @@
accounts: [], accounts: [],
path: "/", path: "/",
paths: [ paths: [
{"text": "/", "value": "/"} {"abs": "/"}
], ],
pathItems: [], pathItems: [],
newPath: "", newPath: "",
@ -104,7 +104,7 @@
(files) => { (files) => {
this.loading = false; this.loading = false;
if(files.length === 1) { if (files.length === 1) {
this.$notify.success("One photo shared"); this.$notify.success("One photo shared");
} else { } else {
this.$notify.success(files.length + " photos shared"); this.$notify.success(files.length + " photos shared");
@ -115,13 +115,12 @@
).catch(() => this.loading = false); ).catch(() => this.loading = false);
}, },
onChange() { onChange() {
this.paths = [{"text": "/", "value": "/"}]; this.paths = [{"abs": "/"}];
this.loading = true; this.loading = true;
this.account.Ls().then(l => { this.account.Dirs().then(p => {
console.log("result", l); for (let i = 0; i < p.length; i++) {
for (let i = 0; i < l.length; i++) { this.paths.push(p[i]);
this.paths.push({"text": l[i], "value": l[i]});
} }
this.pathItems = [...this.paths]; this.pathItems = [...this.paths];
@ -160,7 +159,7 @@
this.newPath = ""; this.newPath = "";
} else { } else {
this.newPath = q; this.newPath = q;
this.pathItems = this.paths.concat([{"text": q, "value": q}]); this.pathItems = this.paths.concat([{"abs": q}]);
} }
}, },
show: function (show) { show: function (show) {

View file

@ -46,20 +46,8 @@ class Account extends Abstract {
return this.ID; return this.ID;
} }
toggleShare() { Dirs() {
const values = { AccShare: !this.AccShare }; return Api.get(this.getEntityResource() + "/dirs").then((response) => Promise.resolve(response.data));
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));
} }
Share(UUIDs, dest) { Share(UUIDs, dest) {

View file

@ -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: // Parameters:
// id: string Account ID as returned by the API // id: string Account ID as returned by the API
func LsAccount(router *gin.RouterGroup, conf *config.Config) { func GetAccountDirs(router *gin.RouterGroup, conf *config.Config) {
router.GET("/accounts/:id/ls", func(c *gin.Context) { router.GET("/accounts/:id/dirs", func(c *gin.Context) {
if Unauthorized(c, conf) { if Unauthorized(c, conf) {
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized) c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
return return
@ -90,7 +90,7 @@ func LsAccount(router *gin.RouterGroup, conf *config.Config) {
return return
} }
list, err := m.Ls() list, err := m.Directories()
if err != nil { if err != nil {
log.Errorf("account: %s", err.Error()) log.Errorf("account: %s", err.Error())

View file

@ -9,6 +9,7 @@ import (
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/service/webdav" "github.com/photoprism/photoprism/internal/service/webdav"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/ulule/deepcopier" "github.com/ulule/deepcopier"
) )
@ -83,14 +84,14 @@ func (m *Account) Delete(db *gorm.DB) error {
return db.Delete(m).Error return db.Delete(m).Error
} }
// Ls returns a list of directories or albums in an account. // Directories returns a list of directories or albums in an account.
func (m *Account) Ls() (result []string, err error) { func (m *Account) Directories() (result fs.FileInfos, err error) {
if m.AccType == string(service.TypeWebDAV) { if m.AccType == string(service.TypeWebDAV) {
c := webdav.Connect(m.AccURL, m.AccUser, m.AccPass) c := webdav.Connect(m.AccURL, m.AccUser, m.AccPass)
result, err = c.Directories("/", true) result, err = c.Directories("/", true)
} }
sort.Strings(result) sort.Sort(result)
return result, err return result, err
} }

View file

@ -13,6 +13,8 @@ type FileSync struct {
FileID uint `gorm:"index;"` FileID uint `gorm:"index;"`
AccountID uint `gorm:"primary_key;auto_increment:false"` AccountID uint `gorm:"primary_key;auto_increment:false"`
RemoteName string `gorm:"type:varbinary(256);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);"` Status string `gorm:"type:varbinary(16);"`
Error string `gorm:"type:varbinary(512);"` Error string `gorm:"type:varbinary(512);"`
File *File File *File
@ -32,6 +34,7 @@ func NewFileSync(accountID uint, remoteName string) *FileSync {
result := &FileSync{ result := &FileSync{
AccountID: accountID, AccountID: accountID,
RemoteName: remoteName, RemoteName: remoteName,
Status: "new",
} }
return result return result

View file

@ -72,7 +72,7 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
api.GetAccounts(v1, conf) api.GetAccounts(v1, conf)
api.GetAccount(v1, conf) api.GetAccount(v1, conf)
api.LsAccount(v1, conf) api.GetAccountDirs(v1, conf)
api.ShareWithAccount(v1, conf) api.ShareWithAccount(v1, conf)
api.CreateAccount(v1, conf) api.CreateAccount(v1, conf)
api.DeleteAccount(v1, conf) api.DeleteAccount(v1, conf)

View file

@ -14,6 +14,7 @@ import (
"path" "path"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/studio-b12/gowebdav" "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. // 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) files, err := c.readDir(dir)
if err != nil { if err != nil {
@ -52,14 +53,17 @@ func (c Client) Files(dir string) (result []string, err error) {
if !file.Mode().IsRegular() { if !file.Mode().IsRegular() {
continue continue
} }
result = append(result, fmt.Sprintf("%s/%s", dir, file.Name()))
info := fs.NewFileInfo(file, dir)
result = append(result, info)
} }
return result, nil return result, nil
} }
// Directories returns all sub directories in path as string slice. // 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) files, err := c.readDir(root)
if err != nil { if err != nil {
@ -75,12 +79,12 @@ func (c Client) Directories(root string, recursive bool) (result []string, err e
continue continue
} }
dir := fmt.Sprintf("%s/%s", root, file.Name()) info := fs.NewFileInfo(file, root)
result = append(result, dir) result = append(result, info)
if recursive { if recursive {
subDirs, err := c.Directories(dir, true) subDirs, err := c.Directories(info.Abs, true)
if err != nil { if err != nil {
return result, err return result, err
@ -125,7 +129,7 @@ func (c Client) DownloadDir(from, to string, recursive bool) (errs []error) {
} }
for _, file := range files { for _, file := range files {
dest := to + string(os.PathSeparator) + file dest := to + string(os.PathSeparator) + file.Abs
if _, err := os.Stat(dest); err == nil { if _, err := os.Stat(dest); err == nil {
// File exists // File exists
@ -135,7 +139,7 @@ func (c Client) DownloadDir(from, to string, recursive bool) (errs []error) {
continue continue
} }
if err := c.Download(file, dest); err != nil { if err := c.Download(file.Abs, dest); err != nil {
msg := fmt.Errorf("webdav: %s", err) msg := fmt.Errorf("webdav: %s", err)
errs = append(errs, msg) errs = append(errs, msg)
log.Error(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) dirs, err := c.Directories(from, false)
for _, dir := range dirs { for _, dir := range dirs {
errs = append(errs, c.DownloadDir(dir, to, true)...) errs = append(errs, c.DownloadDir(dir.Abs, to, true)...)
} }
return errs return errs

View file

@ -53,7 +53,11 @@ func TestClient_Directories(t *testing.T) {
t.Fatal("no directories found") 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) { t.Run("recursive", func(t *testing.T) {
@ -87,7 +91,7 @@ func TestClient_Download(t *testing.T) {
t.Fatal("no files to download") 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) t.Fatal(err)
} }

58
pkg/fs/fileinfo.go Normal file
View 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
View 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)
}