307 lines
8.0 KiB
Go
307 lines
8.0 KiB
Go
package ncworker
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
"rpjosh.de/ncDocConverter/internal/models"
|
|
"rpjosh.de/ncDocConverter/pkg/logger"
|
|
"rpjosh.de/ncDocConverter/web"
|
|
)
|
|
|
|
type convertJob struct {
|
|
job *models.ConvertJob
|
|
user *models.User
|
|
}
|
|
|
|
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 []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"`
|
|
} `xml:"response"`
|
|
}
|
|
|
|
type ncFiles struct {
|
|
extension string
|
|
path string
|
|
lastModified time.Time
|
|
contentType string
|
|
size int
|
|
fileid int
|
|
}
|
|
|
|
type searchTemplateData struct {
|
|
Username string
|
|
Directory string
|
|
ContentType []string
|
|
}
|
|
|
|
func NewJob(job *models.ConvertJob, user *models.User) *convertJob {
|
|
convJob := &convertJob{
|
|
job: job,
|
|
user: user,
|
|
|
|
}
|
|
|
|
return convJob
|
|
}
|
|
|
|
func (job *convertJob) ExecuteJob() {
|
|
source := job.searchInDirectory(
|
|
job.job.SourceDir,
|
|
[]string {
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
"application/msword",
|
|
},
|
|
)
|
|
destination := job.searchInDirectory(
|
|
job.job.DestinationDir,
|
|
[]string {
|
|
"application/pdf",
|
|
},
|
|
)
|
|
|
|
|
|
preCount := len("/remote.php/dav/files/" + job.user.Username + "/")
|
|
// store the files in a map
|
|
sourceMap := make(map[string]ncFiles)
|
|
destinationMap := make(map[string]ncFiles)
|
|
|
|
for _, file := range source.Response {
|
|
path := file.Href[preCount:]
|
|
var extension = filepath.Ext(path)
|
|
var name = path[0:len(path)-len(extension)][len(job.job.SourceDir):]
|
|
// Time format: Fri, 23 Sep 2022 05:46:31 GMT
|
|
time, err := time.Parse("Mon, 02 Jan 2006 15:04:05 GMT", file.Propstat.Prop.Getlastmodified)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
size, err := strconv.Atoi(file.Propstat.Prop.Size)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
sourceMap[name] = ncFiles{
|
|
extension: extension,
|
|
path: path,
|
|
lastModified: time,
|
|
size: size,
|
|
contentType: file.Propstat.Prop.Getcontenttype,
|
|
fileid: file.Propstat.Prop.Fileid,
|
|
}
|
|
}
|
|
|
|
for _, file := range destination.Response {
|
|
path := file.Href[preCount:]
|
|
var extension = filepath.Ext(path)
|
|
var name = path[0:len(path)-len(extension)][len(job.job.DestinationDir):]
|
|
|
|
time, err := time.Parse("Mon, 02 Jan 2006 15:04:05 GMT", file.Propstat.Prop.Getlastmodified)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
size, err := strconv.Atoi(file.Propstat.Prop.Size)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
destinationMap[name] = ncFiles{
|
|
extension: extension,
|
|
path: path,
|
|
lastModified: time,
|
|
size: size,
|
|
contentType: file.Propstat.Prop.Getcontenttype,
|
|
fileid: file.Propstat.Prop.Fileid,
|
|
}
|
|
}
|
|
|
|
for index, source := range sourceMap {
|
|
// check if the file exists in the destination map
|
|
if dest, exists := destinationMap[index]; exists {
|
|
// compare timestamp and size
|
|
if dest.lastModified.Before(source.lastModified) {
|
|
job.convertFile(source.path, source.fileid, dest.path)
|
|
}
|
|
delete(destinationMap, index)
|
|
} else {
|
|
job.convertFile(
|
|
source.path, source.fileid, job.getDestinationDir(source.path),
|
|
)
|
|
delete(destinationMap, index)
|
|
}
|
|
}
|
|
|
|
// delete the files which are not available anymore
|
|
for _, dest := range destinationMap {
|
|
job.deleteFile(dest.path)
|
|
}
|
|
}
|
|
|
|
func (job *convertJob) getDestinationDir(sourceFile string) string {
|
|
sourceFile = sourceFile[len(job.job.SourceDir):]
|
|
var extension = filepath.Ext(sourceFile)
|
|
var name = sourceFile[0:len(sourceFile)-len(extension)]
|
|
|
|
return job.job.DestinationDir + name + ".pdf"
|
|
}
|
|
|
|
func (job *convertJob) createFoldersRecursively(destinationFile string) {
|
|
s := strings.Split(destinationFile, "/")
|
|
folderTree := ""
|
|
|
|
logger.Debug("Creating directory for file '%s'", destinationFile)
|
|
|
|
// webdav doesn't have an function to create directories recursively
|
|
for _, folder := range s[:len(s) - 1] {
|
|
folderTree += folder + "/"
|
|
|
|
client := http.Client{Timeout: 5 * time.Second}
|
|
req, err := http.NewRequest("MKCOL", job.user.NextcloudBaseUrl + "/remote.php/dav/files/" + job.user.Username + "/" + folderTree, nil)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
req.SetBasicAuth(job.user.Username, job.user.Password)
|
|
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
if (res.StatusCode != 201 && res.StatusCode != 405) {
|
|
|
|
}
|
|
// status code 201 or 405 (already existing)
|
|
}
|
|
}
|
|
|
|
func (job *convertJob) convertFile(sourceFile string, sourceid int, destinationFile string) {
|
|
logger.Debug("Trying to convert %s (%d) to %s", sourceFile, sourceid, destinationFile)
|
|
|
|
job.createFoldersRecursively(destinationFile)
|
|
|
|
client := http.Client{Timeout: 10 * time.Second}
|
|
req, err := http.NewRequest(http.MethodGet, job.user.NextcloudBaseUrl + "/apps/onlyoffice/downloadas", nil)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
req.SetBasicAuth(job.user.Username, job.user.Password)
|
|
|
|
q := req.URL.Query()
|
|
q.Add("fileId", fmt.Sprint(sourceid))
|
|
q.Add("toExtension", "pdf")
|
|
req.URL.RawQuery = q.Encode()
|
|
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
// Status Code 200
|
|
defer res.Body.Close()
|
|
|
|
uploadClient := http.Client{Timeout: 10 * time.Second}
|
|
uploadReq, err := http.NewRequest(http.MethodPut, job.user.NextcloudBaseUrl + "/remote.php/dav/files/" + job.user.Username + "/" + destinationFile, res.Body)
|
|
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
uploadReq.SetBasicAuth(job.user.Username, job.user.Password)
|
|
uploadReq.Header.Set("Content-Type", "application/binary")
|
|
|
|
res, err = uploadClient.Do(uploadReq)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
|
|
if (res.StatusCode != 204 && res.StatusCode != 201) {
|
|
logger.Error("Failed to create file %s (#%d)", destinationFile, res.StatusCode)
|
|
}
|
|
// Status Code 201
|
|
res.Body.Close()
|
|
}
|
|
|
|
func (job *convertJob) deleteFile(filePath string) {
|
|
client := http.Client{Timeout: 5 * time.Second}
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, job.user.NextcloudBaseUrl + "/remote.php/dav/files/" + job.user.Username + "/" + filePath, nil)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
req.SetBasicAuth(job.user.Username, job.user.Password)
|
|
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
|
|
if (res.StatusCode != 204) {
|
|
logger.Error("Failed to delete file %s (%d)", filePath, res.StatusCode)
|
|
}
|
|
}
|
|
|
|
// Searches all doc files in the source directory
|
|
func (job *convertJob) searchInDirectory(directory string, contentType []string) *searchResult {
|
|
client := http.Client{Timeout: 5 * time.Second}
|
|
|
|
template, err := template.ParseFS(web.ApiTemplateFiles, "apitemplate/ncsearch.tmpl.xml")
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
var buf bytes.Buffer
|
|
templateData := searchTemplateData{
|
|
Username: job.user.Username,
|
|
Directory: directory,
|
|
ContentType: contentType,
|
|
}
|
|
if err = template.Execute(&buf, templateData); err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
// Status code 207
|
|
req, err := http.NewRequest("SEARCH", job.user.NextcloudBaseUrl + "/remote.php/dav/", &buf)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
req.SetBasicAuth(job.user.Username, job.user.Password)
|
|
req.Header.Set("Content-Type", "application/xml")
|
|
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
resBody, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
|
|
fmt.Print(res.StatusCode)
|
|
var result searchResult
|
|
if err = xml.Unmarshal(resBody, &result); err != nil {
|
|
logger.Error("%s", err)
|
|
}
|
|
|
|
return &result
|
|
} |