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 }