package nextcloud import ( "context" "encoding/xml" "fmt" "io" "net/http" "strconv" "strings" ) const propfindRevisionBody = ` ` // FileRevision holds stable Nextcloud identifiers for a file (same across sharees). type FileRevision struct { FileID int64 ETag string } func (c *Client) FileRevision(ctx context.Context, userID, filePath string) (FileRevision, error) { filePath = NormalizeClientFilePath(userID, filePath) davPath := c.WebDAVPath(userID, filePath) resp, err := c.DoAsUser(ctx, "PROPFIND", davPath, strings.NewReader(propfindRevisionBody), userID, map[string]string{ "Depth": "0", "Content-Type": "application/xml", }) if err != nil { return FileRevision{}, err } defer resp.Body.Close() if resp.StatusCode != http.StatusMultiStatus && resp.StatusCode != http.StatusOK { return FileRevision{}, &HTTPStatusError{Operation: "propfind file revision", StatusCode: resp.StatusCode} } var ms struct { Responses []struct { Propstat struct { Prop struct { FileID string `xml:"http://owncloud.org/ns fileid"` ETag string `xml:"getetag"` } `xml:"prop"` } `xml:"propstat"` } `xml:"response"` } if err := xml.NewDecoder(resp.Body).Decode(&ms); err != nil { return FileRevision{}, err } if len(ms.Responses) == 0 { return FileRevision{}, fmt.Errorf("file revision propfind: empty response") } raw := strings.TrimSpace(ms.Responses[0].Propstat.Prop.FileID) if raw == "" { return FileRevision{}, fmt.Errorf("file revision propfind: missing fileid") } id, err := strconv.ParseInt(raw, 10, 64) if err != nil { return FileRevision{}, fmt.Errorf("file revision propfind: invalid fileid %q", raw) } etag := strings.Trim(strings.TrimSpace(ms.Responses[0].Propstat.Prop.ETag), "\"") return FileRevision{FileID: id, ETag: etag}, nil } func (c *Client) FileID(ctx context.Context, userID, filePath string) (int64, error) { rev, err := c.FileRevision(ctx, userID, filePath) if err != nil { return 0, err } return rev.FileID, nil } func (c *Client) Preview(ctx context.Context, userID string, fileID int64, width, height int) (io.ReadCloser, string, error) { if width <= 0 { width = 400 } if height <= 0 { height = 300 } if width > 2048 { width = 2048 } if height > 2048 { height = 2048 } path := fmt.Sprintf( "/index.php/core/preview?fileId=%d&x=%d&y=%d&a=1&mode=cover&mimeFallback=true", fileID, width, height, ) resp, err := c.DoAsUser(ctx, "GET", path, nil, userID, nil) if err != nil { return nil, "", err } if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, "", &HTTPStatusError{Operation: "preview", StatusCode: resp.StatusCode} } contentType := resp.Header.Get("Content-Type") if contentType == "" { contentType = "image/jpeg" } return resp.Body, contentType, nil }