179 lines
4.9 KiB
Go
179 lines
4.9 KiB
Go
|
package nextcloud
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/xml"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"strings"
|
||
|
"text/template"
|
||
|
"time"
|
||
|
|
||
|
"rpjosh.de/ncDocConverter/internal/models"
|
||
|
"rpjosh.de/ncDocConverter/pkg/logger"
|
||
|
"rpjosh.de/ncDocConverter/web"
|
||
|
)
|
||
|
|
||
|
type searchTemplateData struct {
|
||
|
Username string
|
||
|
Directory string
|
||
|
ContentType []string
|
||
|
}
|
||
|
|
||
|
type searchResult struct {
|
||
|
XMLName xml.Name `xml:"multistatus"`
|
||
|
Text string `xml:",chardata"`
|
||
|
D string `xml:"d,attr"`
|
||
|
S string `xml:"s,attr"`
|
||
|
Oc string `xml:"oc,attr"`
|
||
|
Nc string `xml:"nc,attr"`
|
||
|
Response []searchResultResponse `xml:"response"`
|
||
|
}
|
||
|
type searchResultResponse struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Href string `xml:"href"`
|
||
|
Propstat struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Prop struct {
|
||
|
Text string `xml:",chardata"`
|
||
|
Getcontenttype string `xml:"getcontenttype"`
|
||
|
Getlastmodified string `xml:"getlastmodified"`
|
||
|
Size string `xml:"size"`
|
||
|
Fileid int `xml:"fileid"`
|
||
|
} `xml:"prop"`
|
||
|
Status string `xml:"status"`
|
||
|
} `xml:"propstat"`
|
||
|
}
|
||
|
|
||
|
func (r *searchResultResponse) GetLastModified() time.Time {
|
||
|
// Time format: Fri, 23 Sep 2022 05:46:31 GMT
|
||
|
rtc, err := time.Parse("Mon, 02 Jan 2006 15:04:05 GMT", r.Propstat.Prop.Getlastmodified)
|
||
|
if err != nil {
|
||
|
logger.Warning("%s", err)
|
||
|
rtc = time.Unix(0, 1)
|
||
|
}
|
||
|
|
||
|
return rtc
|
||
|
}
|
||
|
|
||
|
// Returns a new request to the Nexcloud API.
|
||
|
// The path beginning AFTER /dav/ should be given (e.g.: myUser/folder/file.txt)
|
||
|
func getRequest(method string, path string, body io.Reader, ncUser *models.NextcloudUser) *http.Request {
|
||
|
req, err := http.NewRequest(method, ncUser.NextcloudBaseUrl+"/remote.php/dav/"+path, body)
|
||
|
if err != nil {
|
||
|
logger.Error("%s", err)
|
||
|
}
|
||
|
req.SetBasicAuth(ncUser.Username, ncUser.Password)
|
||
|
|
||
|
return req
|
||
|
}
|
||
|
|
||
|
// Searches for all files of the given content type starting in the given directory.
|
||
|
func SearchInDirectory(ncUser *models.NextcloudUser, directory string, contentType []string) (*searchResult, error) {
|
||
|
client := http.Client{Timeout: 5 * time.Second}
|
||
|
|
||
|
template, err := template.ParseFS(web.ApiTemplateFiles, "apitemplate/ncsearch.tmpl.xml")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
var buf bytes.Buffer
|
||
|
templateData := searchTemplateData{
|
||
|
Username: ncUser.Username,
|
||
|
Directory: directory,
|
||
|
ContentType: contentType,
|
||
|
}
|
||
|
if err = template.Execute(&buf, templateData); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Status code 207
|
||
|
req := getRequest("SEARCH", "", &buf, ncUser)
|
||
|
req.Header.Set("Content-Type", "application/xml")
|
||
|
|
||
|
res, err := client.Do(req)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Decody body first before checking status code to print in error message
|
||
|
defer res.Body.Close()
|
||
|
|
||
|
resBody, err := io.ReadAll(res.Body)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if res.StatusCode != 207 {
|
||
|
return nil, fmt.Errorf("status code %d: %s", res.StatusCode, resBody)
|
||
|
}
|
||
|
|
||
|
var result searchResult
|
||
|
if err = xml.Unmarshal(resBody, &result); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return &result, nil
|
||
|
}
|
||
|
|
||
|
// Delets a file with the given path.
|
||
|
// The path has to start at the root level: Ebook/myFolder/file.txt
|
||
|
func DeleteFile(ncUser *models.NextcloudUser, filePath string) error {
|
||
|
client := http.Client{Timeout: 5 * time.Second}
|
||
|
|
||
|
req := getRequest(http.MethodDelete, "files/"+ncUser.Username+"/"+filePath, nil, ncUser)
|
||
|
|
||
|
res, err := client.Do(req)
|
||
|
if err != nil {
|
||
|
logger.Error("%s", err)
|
||
|
}
|
||
|
|
||
|
if res.StatusCode != 204 {
|
||
|
return fmt.Errorf("failed to delete file %s (%d)", filePath, res.StatusCode)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Creates all required directorys to create the destination file recursively.
|
||
|
// The path should be relative to the root: ebook/folder1/folder2/file.txt
|
||
|
func CreateFoldersRecursively(ncUser *models.NextcloudUser, destinationFile string) {
|
||
|
s := strings.Split(destinationFile, "/")
|
||
|
folderTree := ""
|
||
|
|
||
|
// Webdav doesn't have a function to create directories recursively → iterate
|
||
|
for _, folder := range s[:len(s)-1] {
|
||
|
folderTree += folder + "/"
|
||
|
|
||
|
client := http.Client{Timeout: 5 * time.Second}
|
||
|
req := getRequest("MKCOL", "files/"+ncUser.Username+"/"+folderTree, nil, ncUser)
|
||
|
|
||
|
res, err := client.Do(req)
|
||
|
if err != nil {
|
||
|
logger.Error("%s", err)
|
||
|
}
|
||
|
|
||
|
if res.StatusCode != 201 && res.StatusCode != 405 {
|
||
|
logger.Error("Failed to create directorys")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Uploads a file to the nextcloud server.
|
||
|
// It will be saved to the destination as a relative path to the nextcloud root (ebook/file.txt).
|
||
|
func UploadFile(ncUser *models.NextcloudUser, destination string, content io.ReadCloser) error {
|
||
|
client := http.Client{Timeout: 5 * time.Second}
|
||
|
req := getRequest(http.MethodPut, "files/"+ncUser.Username+"/"+destination, content, ncUser)
|
||
|
|
||
|
res, err := client.Do(req)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if res.StatusCode != 201 && res.StatusCode != 204 {
|
||
|
return fmt.Errorf("expected status code 201 or 204 but got %d", res.StatusCode)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|