154 lines
4.1 KiB
Go
154 lines
4.1 KiB
Go
package nextcloud
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const propfindFileProps = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
|
|
<d:prop>
|
|
<d:getlastmodified/>
|
|
<d:getetag/>
|
|
<d:getcontenttype/>
|
|
<d:getcontentlength/>
|
|
<d:resourcetype/>
|
|
<oc:fileid/>
|
|
<oc:size/>
|
|
<oc:favorite/>
|
|
<oc:share-types/>
|
|
<d:displayname/>
|
|
</d:prop>
|
|
</d:propfind>`
|
|
|
|
func (c *Client) StatFile(ctx context.Context, userID, filePath string) (FileInfo, error) {
|
|
filePath = NormalizeClientFilePath(userID, filePath)
|
|
davPath := c.WebDAVPath(userID, filePath)
|
|
|
|
resp, err := c.DoAsUser(ctx, "PROPFIND", davPath, strings.NewReader(propfindFileProps), userID, map[string]string{
|
|
"Depth": "0",
|
|
"Content-Type": "application/xml",
|
|
})
|
|
if err != nil {
|
|
return FileInfo{}, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusMultiStatus && resp.StatusCode != http.StatusOK {
|
|
return FileInfo{}, &HTTPStatusError{Operation: "propfind stat file", StatusCode: resp.StatusCode}
|
|
}
|
|
|
|
return parseSinglePropfindResponse(resp.Body)
|
|
}
|
|
|
|
func (c *Client) FindFileByID(ctx context.Context, userID string, fileID int64) (FileInfo, error) {
|
|
if fileID <= 0 {
|
|
return FileInfo{}, fmt.Errorf("invalid file id")
|
|
}
|
|
userSeg := strings.TrimSpace(userID)
|
|
searchBody := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
|
<d:searchrequest xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
|
<d:basicsearch>
|
|
<d:select>
|
|
<d:prop>
|
|
<oc:fileid/>
|
|
<d:displayname/>
|
|
<d:getcontenttype/>
|
|
<d:getcontentlength/>
|
|
<d:getlastmodified/>
|
|
<d:getetag/>
|
|
<d:resourcetype/>
|
|
<oc:size/>
|
|
<oc:favorite/>
|
|
<oc:share-types/>
|
|
</d:prop>
|
|
</d:select>
|
|
<d:from>
|
|
<d:scope>
|
|
<d:href>/files/%s</d:href>
|
|
<d:depth>infinity</d:depth>
|
|
</d:scope>
|
|
</d:from>
|
|
<d:where>
|
|
<d:eq>
|
|
<d:prop><oc:fileid/></d:prop>
|
|
<d:literal>%d</d:literal>
|
|
</d:eq>
|
|
</d:where>
|
|
<d:orderby/>
|
|
<d:limit><d:nresults>1</d:nresults></d:limit>
|
|
</d:basicsearch>
|
|
</d:searchrequest>`, userSeg, fileID)
|
|
|
|
resp, err := c.DoAsUser(ctx, "SEARCH", "/remote.php/dav/", strings.NewReader(searchBody), userID, map[string]string{
|
|
"Content-Type": "text/xml",
|
|
})
|
|
if err != nil {
|
|
return FileInfo{}, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusMultiStatus && resp.StatusCode != http.StatusOK {
|
|
return FileInfo{}, &HTTPStatusError{Operation: "search file by id", StatusCode: resp.StatusCode}
|
|
}
|
|
|
|
return parseSinglePropfindResponse(resp.Body)
|
|
}
|
|
|
|
func parseSinglePropfindResponse(body io.Reader) (FileInfo, error) {
|
|
var ms multistatus
|
|
if err := xml.NewDecoder(body).Decode(&ms); err != nil {
|
|
return FileInfo{}, err
|
|
}
|
|
if len(ms.Responses) == 0 {
|
|
return FileInfo{}, fmt.Errorf("file not found")
|
|
}
|
|
return fileInfoFromDAVResponse(ms.Responses[0]), nil
|
|
}
|
|
|
|
func fileInfoFromDAVResponse(r response) FileInfo {
|
|
name := fileNameFromDAVProp(r.Propstat.Prop.DisplayName, r.Href)
|
|
clientPath := clientPathFromDAVHref(r.Href)
|
|
name = SyncFileDisplayName(clientPath, name)
|
|
|
|
fileType := "file"
|
|
if r.Propstat.Prop.ResourceType.Collection != nil {
|
|
fileType = "directory"
|
|
}
|
|
|
|
size := r.Propstat.Prop.ContentLength
|
|
if r.Propstat.Prop.Size > 0 {
|
|
size = r.Propstat.Prop.Size
|
|
}
|
|
|
|
return FileInfo{
|
|
Path: clientPath,
|
|
Name: name,
|
|
Type: fileType,
|
|
Size: size,
|
|
MimeType: r.Propstat.Prop.ContentType,
|
|
LastModified: r.Propstat.Prop.LastModified,
|
|
ETag: strings.Trim(r.Propstat.Prop.ETag, "\""),
|
|
FileID: parseFileID(r.Propstat.Prop.FileID),
|
|
IsFavorite: r.Propstat.Prop.Favorite == 1,
|
|
IsShared: len(r.Propstat.Prop.ShareTypes.ShareType) > 0,
|
|
}
|
|
}
|
|
|
|
func ParseDriveFileID(raw string) (int64, error) {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return 0, fmt.Errorf("empty file id")
|
|
}
|
|
id, err := strconv.ParseInt(raw, 10, 64)
|
|
if err != nil || id <= 0 {
|
|
return 0, fmt.Errorf("invalid file id %q", raw)
|
|
}
|
|
return id, nil
|
|
}
|