-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #49 from gary-kim/enh/noid/file-uploads
- Loading branch information
Showing
37 changed files
with
9,410 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package ocs | ||
|
||
const ( | ||
// ShareTypeRoom is OC.share.SHARE_TYPE_ROOM | ||
ShareTypeRoom = "10" | ||
) | ||
|
||
// ShareReturn is the response for a file share request | ||
type ShareReturn struct { | ||
OCS struct { | ||
ocs | ||
Data struct { | ||
URL string `json:"url"` | ||
} `json:"data"` | ||
} `json:"ocs"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package room | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/monaco-io/request" | ||
|
||
"gomod.garykim.dev/nc-talk/constants" | ||
"gomod.garykim.dev/nc-talk/ocs" | ||
) | ||
|
||
// ShareFile shares the file at the given path with the talk room | ||
func (t *TalkRoom) ShareFile(path string) (string, error) { | ||
req := t.User.RequestClient(request.Client{ | ||
URL: constants.FilesSharingEndpoint + "shares", | ||
Params: map[string]string{ | ||
"shareType": ocs.ShareTypeRoom, | ||
"path": path, | ||
"shareWith": t.Token, | ||
}, | ||
}) | ||
resp, err := req.Do() | ||
if err != nil { | ||
return "", err | ||
} | ||
if resp.StatusCode() != http.StatusOK { | ||
return "", ErrUnexpectedReturnCode | ||
} | ||
data := &ocs.ShareReturn{} | ||
err = json.Unmarshal(resp.Data, data) | ||
if err != nil { | ||
return "", err | ||
} | ||
return data.OCS.Data.URL, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package user | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
"path" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
|
||
"gomod.garykim.dev/nc-talk/constants" | ||
|
||
"github.com/studio-b12/gowebdav" | ||
) | ||
|
||
var ( | ||
duplicateSuffixRegex = regexp.MustCompile(` \((\d+)\)$`) | ||
|
||
// ErrIsNotDirectory is returned if a path expected to be a directory is not a directory | ||
ErrIsNotDirectory = errors.New("is not a directory") | ||
) | ||
|
||
// UploadFile uploads the given data to the talk attachments | ||
// directory and returns the filepath it was uploaded at. | ||
// | ||
// If the directory does not exist, the function will create the directory. | ||
// | ||
// Provide only the name of the file, not the full path. If a full path is provided, | ||
// only the basename will be used. Use user.TalkUser.UploadFileAtPath for uploading | ||
// to a specific directory. | ||
func (t *TalkUser) UploadFile(data *[]byte, name string) (finalPath string, err error) { | ||
capabilities, err := t.Capabilities() | ||
if err != nil { | ||
return | ||
} | ||
finalPath = path.Clean(capabilities.AttachmentsFolder + "/" + path.Base(name)) | ||
finalPath, err = t.uploadFilePath(finalPath) | ||
if err != nil { | ||
return | ||
} | ||
err = t.UploadFileAtPath(data, finalPath) | ||
return | ||
} | ||
|
||
// UploadFileAtPath uploads the given data at the given path. | ||
func (t *TalkUser) UploadFileAtPath(data *[]byte, givenPath string) error { | ||
c := t.getWebdavClient() | ||
err := c.Write(givenPath, *data, 0644) | ||
if err != nil { | ||
if ferr, ok := err.(*os.PathError); !ok || strings.HasPrefix(ferr.Err.Error(), "404") { | ||
err = c.MkdirAll(path.Dir(givenPath), 0644) | ||
if err != nil { | ||
return err | ||
} | ||
err = c.Write(givenPath, *data, 0644) | ||
} | ||
} | ||
return err | ||
} | ||
|
||
// uploadFilePath provides a unique path to upload a file with the given path. | ||
func (t *TalkUser) uploadFilePath(name string) (finalPath string, err error) { | ||
c := t.getWebdavClient() | ||
|
||
capabilities, err := t.Capabilities() | ||
if err != nil { | ||
return | ||
} | ||
|
||
statInfo, err := c.Stat(capabilities.AttachmentsFolder) | ||
if err != nil { | ||
// The error may be because it does not exist | ||
if strings.HasPrefix(err.(*os.PathError).Err.Error(), "404 Not Found - PROPFIND") { | ||
// Directory does not exist. Return the given path directly. | ||
return name, nil | ||
} | ||
return | ||
} | ||
if !statInfo.IsDir() { | ||
return "", ErrIsNotDirectory | ||
} | ||
|
||
files, err := c.ReadDir(capabilities.AttachmentsFolder) | ||
if err != nil { | ||
return | ||
} | ||
|
||
filename := path.Base(name) | ||
if !includesFileName(files, filename) { | ||
return filename, nil | ||
} | ||
|
||
extension := path.Ext(filename) | ||
basename := strings.TrimSuffix(filename, extension) | ||
|
||
suffix := duplicateSuffixRegex.Find([]byte(basename)) | ||
nameWithoutSuffix := strings.TrimSuffix(basename, string(suffix)) | ||
|
||
// Loop until a unique path is found | ||
for i := 2; true; i++ { | ||
uniqueName := nameWithoutSuffix + " (" + strconv.Itoa(i) + ")" + extension | ||
if !includesFileName(files, uniqueName) { | ||
finalPath = path.Dir(name) + "/" + uniqueName | ||
return | ||
} | ||
} | ||
return "", errors.New("should never reach") | ||
} | ||
|
||
func includesFileName(filelist []os.FileInfo, name string) bool { | ||
for _, v := range filelist { | ||
if v.Name() == name { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (t *TalkUser) getWebdavClient() *gowebdav.Client { | ||
if t.webdavclient == nil { | ||
url := t.NextcloudURL + constants.RemoteDavEndpoint(t.User, "files") | ||
t.webdavclient = gowebdav.NewClient(url, t.User, t.Pass) | ||
} | ||
return t.webdavclient | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package user | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"net/http" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/monaco-io/request" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"golang.org/x/net/webdav" | ||
) | ||
|
||
func startTestServer() (int, error) { | ||
wd := &webdav.Handler{ | ||
Prefix: "/remote.php/dav/files/testuser/", | ||
FileSystem: webdav.NewMemFS(), | ||
LockSystem: webdav.NewMemLS(), | ||
} | ||
|
||
listener, err := net.Listen("tcp", ":0") | ||
if err != nil { | ||
return 0, err | ||
} | ||
port := listener.Addr().(*net.TCPAddr).Port | ||
go func() { | ||
fmt.Println("Serving on port: " + strconv.Itoa(port)) | ||
err := http.Serve(listener, wd) | ||
if err != nil { | ||
panic(err) | ||
} | ||
}() | ||
return port, err | ||
} | ||
|
||
func TestFileUploads(t *testing.T) { | ||
port, err := startTestServer() | ||
assert.NoError(t, err, "Starting server") | ||
|
||
user, err := NewUser("http://localhost:"+strconv.Itoa(port), "testuser", "password", nil) | ||
assert.NoError(t, err, "Creating a new user") | ||
|
||
user.capabilities = &Capabilities{ | ||
AttachmentsFolder: "/Talk", | ||
AttachmentsAllowed: true, | ||
} | ||
|
||
someBytes := &[]byte{21, 65, 56, 57, 2, 95, 100, 85} | ||
|
||
filepath, err := user.UploadFile(someBytes, "testfile.txt") | ||
assert.NoError(t, err, "creating testfile.txt") | ||
assert.Equal(t, "/Talk/testfile.txt", filepath, "ensuring file is in the expected location") | ||
|
||
c := user.RequestClient(request.Client{ | ||
URL: "/remote.php/dav/files/testuser/Talk/testfile.txt", | ||
}) | ||
resp, err := c.Do() | ||
assert.NoError(t, err, "downloading file to check it is identical") | ||
assert.Equal(t, someBytes, &resp.Data, "checking that the data is the same after being downloaded") | ||
|
||
filepath, err = user.UploadFile(someBytes, "testfile.txt") | ||
assert.NoError(t, err, "creating a second testfile.txt") | ||
assert.Equal(t, "/Talk/testfile (2).txt", filepath, "ensuring file is in the expected location") | ||
|
||
c = user.RequestClient(request.Client{ | ||
URL: "/remote.php/dav/files/testuser/Talk/testfile (2).txt", | ||
}) | ||
resp, err = c.Do() | ||
assert.NoError(t, err, "downloading file to check it is identical") | ||
assert.Equal(t, someBytes, &resp.Data, "checking that the data is the same after being downloaded") | ||
|
||
filepath, err = user.UploadFile(someBytes, "testfile.txt") | ||
assert.NoError(t, err, "creating a third testfile.txt") | ||
assert.Equal(t, "/Talk/testfile (3).txt", filepath, "ensuring file is in the expected location") | ||
|
||
c = user.RequestClient(request.Client{ | ||
URL: "/remote.php/dav/files/testuser/Talk/testfile (3).txt", | ||
}) | ||
resp, err = c.Do() | ||
assert.NoError(t, err, "downloading file to check it is identical") | ||
assert.Equal(t, someBytes, &resp.Data, "checking that the data is the same after being downloaded") | ||
|
||
filepath, err = user.UploadFile(someBytes, "testfile (3).txt") | ||
assert.NoError(t, err, "creating a file with an already existing increment: testfile (2).txt") | ||
assert.Equal(t, "/Talk/testfile (4).txt", filepath, "ensuring file is in the expected location") | ||
|
||
err = user.UploadFileAtPath(someBytes, "/asdf/asdf/testing.txt") | ||
assert.NoError(t, err, "attempting upload with UploadFileAtPath") | ||
|
||
c = user.RequestClient(request.Client{ | ||
URL: "/remote.php/dav/files/testuser/asdf/asdf/testing.txt", | ||
}) | ||
resp, err = c.Do() | ||
assert.NoError(t, err, "downloading file to check it is identical") | ||
assert.Equal(t, someBytes, &resp.Data, "checking that the data is the same after being downloaded") | ||
|
||
// Sanity check | ||
c = user.RequestClient(request.Client{ | ||
URL: "/remote.php/dav/files/testuser/asdf/asdf/testing bla.txt", | ||
}) | ||
resp, err = c.Do() | ||
assert.NoError(t, err, "downloading file to check it is identical") | ||
assert.Equal(t, http.StatusNotFound, resp.StatusCode(), "making sure a file that does not exist is not found") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.