- Introduced new endpoints for contact discovery, including scanning, listing, and managing discovered contacts. - Implemented retry logic for handling missing DAV credentials during contact operations. - Added public share functionality for drive API, allowing users to manage public shares, including upload, delete, and rename operations. - Updated Nextcloud configuration to support public share links and improved error handling for public share permissions. - Enhanced logging and validation across contact and drive APIs for better error tracking and user feedback. - Added tests for new contact matching and ranking functionalities to ensure accuracy and reliability.
114 lines
2.9 KiB
Go
114 lines
2.9 KiB
Go
package nextcloud
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const propfindRevisionBody = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
|
<d:prop>
|
|
<oc:fileid/>
|
|
<d:getetag/>
|
|
</d:prop>
|
|
</d:propfind>`
|
|
|
|
// 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
|
|
}
|