From 1eaa6a06cb82e9a80bb53821dbab6cf05c42f0a5 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 17 Nov 2024 15:03:56 +0100 Subject: [PATCH 1/4] Implement metadata retrieval for integrations --- internal/app/handlers.go | 22 +++++++++++++++++++--- internal/integrations/dropbox.go | 5 +++++ internal/integrations/integrations.go | 1 + internal/integrations/localfs.go | 19 +++++++++++++++++++ internal/integrations/webdav.go | 19 +++++++++++++++++++ internal/messages/messages.go | 11 +++++++---- 6 files changed, 70 insertions(+), 7 deletions(-) diff --git a/internal/app/handlers.go b/internal/app/handlers.go index 4773eb96..e7e76a4b 100644 --- a/internal/app/handlers.go +++ b/internal/app/handlers.go @@ -908,9 +908,25 @@ func (app *App) blobStorageWrite(c *gin.Context) { } func (app *App) integrationsGetMetadata(c *gin.Context) { - var metadata messages.IntegrationMetadata - metadata.Thumbnail = "" - c.JSON(http.StatusOK, &metadata) + uid := c.GetString(userIDKey) + integrationID := common.ParamS(integrationKey, c) + fileID := common.ParamS(fileKey, c) + + integrationProvider, err := integrations.GetIntegrationProvider(app.userStorer, uid, integrationID) + if err != nil { + log.Error(fmt.Errorf("can't get integration, %v", err)) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + metadata, err := integrationProvider.GetMetadata(fileID) + if err != nil { + log.Error(err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, metadata) } func (app *App) integrationsUpload(c *gin.Context) { diff --git a/internal/integrations/dropbox.go b/internal/integrations/dropbox.go index 41f7113f..7e06f0d8 100644 --- a/internal/integrations/dropbox.go +++ b/internal/integrations/dropbox.go @@ -24,6 +24,11 @@ func newDropbox(i model.IntegrationConfig) *DropBox { } } +func (d *DropBox) GetMetadata(fileID string) (*messages.IntegrationMetadata, error) { + // TODO + return nil, nil +} + func (d *DropBox) List(folderID string, depth int) (*messages.IntegrationFolder, error) { args := files.ListFolderArg{ diff --git a/internal/integrations/integrations.go b/internal/integrations/integrations.go index b4f59dc5..79ddfbe0 100644 --- a/internal/integrations/integrations.go +++ b/internal/integrations/integrations.go @@ -21,6 +21,7 @@ const ( // IntegrationProvider abstracts 3rd party integrations type IntegrationProvider interface { + GetMetadata(fileID string) (result *messages.IntegrationMetadata, err error) List(folderID string, depth int) (result *messages.IntegrationFolder, err error) Download(fileID string) (io.ReadCloser, error) Upload(folderID, name, fileType string, reader io.ReadCloser) (string, error) diff --git a/internal/integrations/localfs.go b/internal/integrations/localfs.go index b194619d..884fab73 100644 --- a/internal/integrations/localfs.go +++ b/internal/integrations/localfs.go @@ -26,6 +26,25 @@ func newLocalFS(i model.IntegrationConfig) *localFS { } } +func (d *localFS) GetMetadata(fileID string) (*messages.IntegrationMetadata, error) { + decoded, err := decodeName(fileID) + if err != nil { + return nil, err + } + + ext := path.Ext(decoded) + contentType := contentTypeFromExt(ext) + + return &messages.IntegrationMetadata{ + ID: fileID, + Name: path.Base(decoded), + Thumbnail: []byte{}, + SourceFileType: contentType, + ProvidedFileType: contentType, + FileType: ext, + }, nil +} + // List populates the response func (d *localFS) List(folder string, depth int) (*messages.IntegrationFolder, error) { response := messages.NewIntegrationFolder(folder, "") diff --git a/internal/integrations/webdav.go b/internal/integrations/webdav.go index 74ab2900..6584f979 100644 --- a/internal/integrations/webdav.go +++ b/internal/integrations/webdav.go @@ -97,6 +97,25 @@ func (w *WebDavIntegration) Download(fileID string) (io.ReadCloser, error) { return w.c.ReadStream(decoded) } +func (w *WebDavIntegration) GetMetadata(fileID string) (*messages.IntegrationMetadata, error) { + decoded, err := decodeName(fileID) + if err != nil { + return nil, err + } + + ext := path.Ext(decoded) + contentType := contentTypeFromExt(ext) + + return &messages.IntegrationMetadata{ + ID: fileID, + Name: path.Base(decoded), + Thumbnail: []byte{}, + SourceFileType: contentType, + ProvidedFileType: contentType, + FileType: ext, + }, nil +} + // List populates the response func (w *WebDavIntegration) List(folder string, depth int) (*messages.IntegrationFolder, error) { response := messages.NewIntegrationFolder(folder, "") diff --git a/internal/messages/messages.go b/internal/messages/messages.go index 87651ff6..77985ed0 100644 --- a/internal/messages/messages.go +++ b/internal/messages/messages.go @@ -225,8 +225,11 @@ type IntegrationFolder struct { } type IntegrationMetadata struct { - FileType string `json:"fileType"` - ID string `json:"id"` - Name string `json:"name"` - Thumbnail string `json:"thumbnail"` + ID string `json:"id"` + Name string `json:"name"` + // Thumbnail is base64 encoded string of an image/png + Thumbnail []byte `json:"thumbnail"` + SourceFileType string `json:"sourceFileType"` + ProvidedFileType string `json:"providedFileType"` + FileType string `json:"fileType"` } From adefc34943400e5dcf9152d73bcebb7678ec373c Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 17 Nov 2024 15:05:05 +0100 Subject: [PATCH 2/4] integrations: Add Size in return to download interface --- internal/app/handlers.go | 5 +++-- internal/integrations/dropbox.go | 4 ++-- internal/integrations/integrations.go | 3 +-- internal/integrations/localfs.go | 13 ++++++++++--- internal/integrations/webdav.go | 14 +++++++++++--- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/internal/app/handlers.go b/internal/app/handlers.go index e7e76a4b..0557c831 100644 --- a/internal/app/handlers.go +++ b/internal/app/handlers.go @@ -968,7 +968,7 @@ func (app *App) integrationsGetFile(c *gin.Context) { return } - reader, err := integrationProvider.Download(fileID) + reader, size, err := integrationProvider.Download(fileID) if err != nil { log.Error(err) c.AbortWithStatus(http.StatusInternalServerError) @@ -977,8 +977,9 @@ func (app *App) integrationsGetFile(c *gin.Context) { defer reader.Close() - c.DataFromReader(http.StatusOK, -1, "application/octet-stream", reader, nil) + c.DataFromReader(http.StatusOK, size, "application/octet-stream", reader, nil) } + func (app *App) integrationsList(c *gin.Context) { uid := c.GetString(userIDKey) integrationID := common.ParamS(integrationKey, c) diff --git a/internal/integrations/dropbox.go b/internal/integrations/dropbox.go index 7e06f0d8..0e539eb6 100644 --- a/internal/integrations/dropbox.go +++ b/internal/integrations/dropbox.go @@ -70,8 +70,8 @@ func (d *DropBox) List(folderID string, depth int) (*messages.IntegrationFolder, return response, nil } -func (d *DropBox) Download(fileID string) (io.ReadCloser, error) { - return nil, nil +func (d *DropBox) Download(fileID string) (io.ReadCloser, int64, error) { + return nil, 0, nil } func (d *DropBox) Upload(folderID, name, fileType string, reader io.ReadCloser) (string, error) { diff --git a/internal/integrations/integrations.go b/internal/integrations/integrations.go index 79ddfbe0..0dfe29d0 100644 --- a/internal/integrations/integrations.go +++ b/internal/integrations/integrations.go @@ -23,7 +23,7 @@ const ( type IntegrationProvider interface { GetMetadata(fileID string) (result *messages.IntegrationMetadata, err error) List(folderID string, depth int) (result *messages.IntegrationFolder, err error) - Download(fileID string) (io.ReadCloser, error) + Download(fileID string) (io.ReadCloser, int64, error) Upload(folderID, name, fileType string, reader io.ReadCloser) (string, error) } @@ -126,7 +126,6 @@ func visitDir(root, currentPath string, depth int, parentFolder *messages.Integr docName := strings.TrimSuffix(entryName, ext) extension := strings.TrimPrefix(ext, ".") - file := &messages.IntegrationFile{ ProvidedFileType: contentType, DateChanged: d.ModTime(), diff --git a/internal/integrations/localfs.go b/internal/integrations/localfs.go index 884fab73..2470903c 100644 --- a/internal/integrations/localfs.go +++ b/internal/integrations/localfs.go @@ -88,16 +88,23 @@ func (d *localFS) List(folder string, depth int) (*messages.IntegrationFolder, e return response, nil } -func (d *localFS) Download(fileID string) (io.ReadCloser, error) { +func (d *localFS) Download(fileID string) (io.ReadCloser, int64, error) { decoded, err := decodeName(fileID) if err != nil { - return nil, err + return nil, 0, err } localPath := path.Join(d.rootPath, path.Clean(decoded)) - return os.Open(localPath) + st, err := os.Stat(localPath) + if err != nil { + return nil, 0, err + } + + res, err := os.Open(localPath) + return res, st.Size(), err } + func (d *localFS) Upload(folderID, name, fileType string, reader io.ReadCloser) (id string, err error) { folder := "/" if folderID != rootFolder { diff --git a/internal/integrations/webdav.go b/internal/integrations/webdav.go index 6584f979..267966d1 100644 --- a/internal/integrations/webdav.go +++ b/internal/integrations/webdav.go @@ -89,12 +89,20 @@ func (w *WebDavIntegration) Upload(folderID, name, fileType string, reader io.Re } // Download downloads -func (w *WebDavIntegration) Download(fileID string) (io.ReadCloser, error) { +func (w *WebDavIntegration) Download(fileID string) (io.ReadCloser, int64, error) { decoded, err := decodeName(fileID) if err != nil { - return nil, err + return nil, 0, err + } + + st, err := w.c.Stat(decoded) + if err != nil { + return nil, 0, err } - return w.c.ReadStream(decoded) + + res, err := w.c.ReadStream(decoded) + + return res, st.Size(), err } func (w *WebDavIntegration) GetMetadata(fileID string) (*messages.IntegrationMetadata, error) { From a5bd8c2ae78a49f039d52509134c4c471b80041f Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 17 Nov 2024 15:05:59 +0100 Subject: [PATCH 3/4] Add API routes to explore integrations content in ui --- internal/ui/handlers.go | 78 +++++++++++++++++++++++++++++++++++++++++ internal/ui/routes.go | 4 +++ 2 files changed, 82 insertions(+) diff --git a/internal/ui/handlers.go b/internal/ui/handlers.go index e30e7823..09e7686d 100644 --- a/internal/ui/handlers.go +++ b/internal/ui/handlers.go @@ -6,6 +6,7 @@ import ( "time" "github.com/ddvk/rmfakecloud/internal/common" + "github.com/ddvk/rmfakecloud/internal/integrations" "github.com/ddvk/rmfakecloud/internal/model" "github.com/ddvk/rmfakecloud/internal/storage" "github.com/ddvk/rmfakecloud/internal/ui/viewmodel" @@ -648,3 +649,80 @@ func (app *ReactAppWrapper) deleteIntegration(c *gin.Context) { c.AbortWithStatus(http.StatusNotFound) } + +func (app *ReactAppWrapper) exploreIntegration(c *gin.Context) { + uid := c.GetString(userIDContextKey) + + integrationID := common.ParamS(intIDParam, c) + + integrationProvider, err := integrations.GetIntegrationProvider(app.userStorer, uid, integrationID) + if err != nil { + log.Error(err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + folder := common.ParamS("path", c) + if folder == "" { + folder = "root" + } + + response, err := integrationProvider.List(folder, 2) + if err != nil { + log.Error(err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, response) +} + +func (app *ReactAppWrapper) getMetadataIntegration(c *gin.Context) { + uid := c.GetString(userIDContextKey) + + integrationID := common.ParamS(intIDParam, c) + + integrationProvider, err := integrations.GetIntegrationProvider(app.userStorer, uid, integrationID) + if err != nil { + log.Error(err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + fileid := common.ParamS("path", c) + + response, err := integrationProvider.GetMetadata(fileid) + if err != nil { + log.Error(err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, response) +} + +func (app *ReactAppWrapper) downloadThroughIntegration(c *gin.Context) { + uid := c.GetString(userIDContextKey) + + integrationID := common.ParamS(intIDParam, c) + + integrationProvider, err := integrations.GetIntegrationProvider(app.userStorer, uid, integrationID) + if err != nil { + log.Error(err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + fileid := common.ParamS("path", c) + + response, size, err := integrationProvider.Download(fileid) + if err != nil { + log.Error(err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + defer response.Close() + + c.DataFromReader(http.StatusOK, size, "", response, nil) +} diff --git a/internal/ui/routes.go b/internal/ui/routes.go index 4df7fd14..5a6f16fc 100644 --- a/internal/ui/routes.go +++ b/internal/ui/routes.go @@ -79,6 +79,10 @@ func (app *ReactAppWrapper) RegisterRoutes(router *gin.Engine) { auth.PUT("integrations/:intid", app.updateIntegration) auth.DELETE("integrations/:intid", app.deleteIntegration) + auth.GET("integrations/:intid/explore/*path", app.exploreIntegration) + auth.GET("integrations/:intid/metadata/*path", app.getMetadataIntegration) + auth.GET("integrations/:intid/download/*path", app.downloadThroughIntegration) + //admin admin := auth.Group("") admin.Use(app.adminMiddleware()) From f190b663f3a37997529bb903bc230a332f8a1467 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 17 Nov 2024 15:26:59 +0100 Subject: [PATCH 4/4] integrations: Use int64 for file size --- internal/integrations/integrations.go | 2 +- internal/messages/messages.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/integrations/integrations.go b/internal/integrations/integrations.go index 0dfe29d0..dc172c10 100644 --- a/internal/integrations/integrations.go +++ b/internal/integrations/integrations.go @@ -134,7 +134,7 @@ func visitDir(root, currentPath string, depth int, parentFolder *messages.Integr ID: encodedPath, FileID: encodedPath, Name: docName, - Size: int(d.Size()), + Size: d.Size(), SourceFileType: contentType, } diff --git a/internal/messages/messages.go b/internal/messages/messages.go index 77985ed0..09ef8fa2 100644 --- a/internal/messages/messages.go +++ b/internal/messages/messages.go @@ -200,7 +200,7 @@ type IntegrationFile struct { ID string `json:"id"` Name string `json:"name"` ProvidedFileType string `json:"providedFileType"` - Size int `json:"size"` + Size int64 `json:"size"` SourceFileType string `json:"sourceFileType"` }