package ncworker import ( "fmt" "io" "net/http" "path/filepath" "strings" "sync" "time" "rpjosh.de/ncDocConverter/internal/models" "rpjosh.de/ncDocConverter/internal/nextcloud" "rpjosh.de/ncDocConverter/pkg/logger" "rpjosh.de/ncDocConverter/pkg/utils" ) type convertJob struct { job *models.NcConvertJob ncUser *models.NextcloudUser } type convertQueu struct { source nextcloud.NcFile destination string } func NewNcJob(job *models.NcConvertJob, ncUser *models.NextcloudUser) *convertJob { convJob := &convertJob{ job: job, ncUser: ncUser, } return convJob } func (job *convertJob) ExecuteJob() { // Get existing directory contents sourceFolder, err := nextcloud.SearchInDirectory( job.ncUser, job.job.SourceDir, []string{ "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/msword", }, ) if err != nil { logger.Error("Failed to get files in source directory '%s': %s", job.job.SourceDir, err) return } destinationFolder, err := nextcloud.SearchInDirectory( job.ncUser, job.job.DestinationDir, []string{ "application/pdf", }, ) if err != nil { logger.Error("Failed to get files in destination directory '%s': %s", job.job.DestinationDir, err) return } // Store all files in a map prefix := "/remote.php/dav/files/" + job.ncUser.Username + "/" sourceMap := nextcloud.ParseSearchResult(sourceFolder, prefix, job.job.SourceDir) destinationMap := nextcloud.ParseSearchResult(destinationFolder, prefix, job.job.DestinationDir) // check which files should be converted var filesToConvert []convertQueu var directorys []string 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) { filesToConvert = append(filesToConvert, convertQueu{source: source, destination: dest.Path}) } delete(destinationMap, index) } else { // the directory could not be existing -> check for existance destinationDir := job.getDestinationDir(source.Path) appendIfNotExists(&directorys, destinationDir[0:strings.LastIndex(destinationDir, "/")+1]) filesToConvert = append(filesToConvert, convertQueu{source: source, destination: destinationDir}) delete(destinationMap, index) } } var wg sync.WaitGroup // Delete the files which are not available anymore wg.Add(len(destinationMap)) for _, dest := range destinationMap { go func(file *nextcloud.NcFile) { err := nextcloud.DeleteFile(job.ncUser, file.Path) if err != nil { logger.Error(utils.FirstCharToUppercase(err.Error())) } wg.Done() }(&dest) } wg.Wait() // Create required directorys wg.Add(len(directorys)) for _, dest := range directorys { go func(path string) { nextcloud.CreateFoldersRecursively(job.ncUser, path) wg.Done() }(dest) } wg.Wait() // Convert the files wg.Add(len(filesToConvert)) for _, file := range filesToConvert { go func(cvt convertQueu) { job.convertFile(cvt.source.Path, cvt.source.Fileid, cvt.destination) wg.Done() }(file) } wg.Wait() logger.Info("Finished Nextcloud job \"%s\": %d documents converted", job.job.JobName, len(filesToConvert)) } // Appends the directory to the array if it isn't contained // by another element already func appendIfNotExists(dirs *[]string, directory string) { directoryLength := len(directory) for i, currentDir := range *dirs { currentLength := len(currentDir) // the existing directory is already referenced in the current if directoryLength > currentLength && directory[0:currentLength] == currentDir { (*dirs)[i] = directory continue } else if directoryLength <= currentLength && currentDir[0:directoryLength] == directory { continue } } *dirs = append(*dirs, directory) } 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" } // Converts the source file to the destination file utilizing the onlyoffice convert api func (job *convertJob) convertFile(sourceFile string, sourceid int, destinationFile string) { logger.Debug("Converting %s (%d) to %s", sourceFile, sourceid, destinationFile) client := http.Client{Timeout: 10 * time.Second} req, err := http.NewRequest(http.MethodGet, job.ncUser.NextcloudBaseUrl+"/apps/onlyoffice/downloadas", nil) if err != nil { logger.Error("%s", err) } req.SetBasicAuth(job.ncUser.Username, job.ncUser.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("Failed to access the convert api: %s", err) return } defer res.Body.Close() if res.StatusCode != 200 { body, _ := io.ReadAll(res.Body) logger.Error("Failed to access the convert api (#%d). Do you have OnlyOffice installed?: %s", res.StatusCode, body) return } uploadClient := http.Client{Timeout: 10 * time.Second} uploadReq, err := http.NewRequest(http.MethodPut, job.ncUser.NextcloudBaseUrl+"/remote.php/dav/files/"+job.ncUser.Username+"/"+destinationFile, res.Body) if err != nil { logger.Error("%s", err) } uploadReq.SetBasicAuth(job.ncUser.Username, job.ncUser.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() }