ultisuite-backend/internal/nextcloud/preview.go
R3D347HR4Y 556d5f416d Enhance API and configuration for contact discovery and public sharing
- 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.
2026-06-06 20:27:02 +02:00

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
}