package nextcloud
import (
"context"
"encoding/xml"
"fmt"
"io"
"net/http"
"strconv"
"strings"
)
const propfindFileProps = `
`
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(`
/files/%s
infinity
%d
1
`, 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
}