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"
: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();
}
}

View file

@ -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) {

View file

@ -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) {

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:
// 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())

View file

@ -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
}

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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
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)
}