More improvements to fileopening
* Add safe-guard to avoid nuking a whole hard-drive due to a potential bug
* save files with their attachment UUID instead of filename, to avoid
clashes
* move main-code of the feature into a new package (file)
ADD
fileopen/fileopen.go
+package fileopen
+
+import (
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/Bios-Marcel/cordless/commands"
+ "github.com/Bios-Marcel/cordless/config"
+ "github.com/Bios-Marcel/cordless/util/files"
+ "github.com/skratchdot/open-golang/open"
+)
+
+var cacheCleanerLock = &sync.Mutex{}
+
+// LaunchCacheCleaner clears all files in the given folder structure that are
+// older than the given timeframe. Root-Paths are ignored for safety reasons.
+func LaunchCacheCleaner(targetFolder string, olderThan time.Duration) {
+ //We try to avoid deleting someones whole hard-drive-content.
+ //Is there a better way to do this?
+ if targetFolder == "" || targetFolder == "/" || (len(targetFolder) == 3 && strings.HasSuffix(targetFolder, ":/")) {
+ return
+ }
+
+ go func() {
+ cacheCleanerLock.Lock()
+ defer cacheCleanerLock.Unlock()
+ now := time.Now().UnixNano()
+ filepath.Walk(targetFolder, func(path string, f os.FileInfo, err error) error {
+ if now-f.ModTime().UnixNano() >= olderThan.Nanoseconds() {
+ removeError := os.Remove(path)
+ if removeError != nil {
+ log.Printf("Couldn't remove file %s from cache.\n", removeError)
+ }
+ }
+
+ return nil
+ })
+ }()
+}
+
+// OpenFile attempts downloading and opening a file from the given link.
+// Files are either cached locally or saved permanently. In both cases
+// cordless attempts loading the previously downloaded version of the
+// file. Files are saved using the ID of the attachment in order to avoid
+// false positives when doing cache-matching.
+func OpenFile(targetFolder, fileID, downloadURL string) error {
+ extension := strings.TrimPrefix(filepath.Ext(downloadURL), ".")
+ targetFile := filepath.Join(targetFolder, fileID+"."+extension)
+ downloadError := files.DownloadFileOrAccessCache(targetFile, downloadURL)
+ if downloadError != nil {
+ return downloadError
+ }
+
+ handler, handlerSet := config.Current.FileOpenHandlers[extension]
+ if handlerSet {
+ handlerTrimmed := strings.TrimSpace(handler)
+ //Empty means to not open files with the given extension.
+ if handlerTrimmed == "" {
+ log.Printf("skip opening link %s, as the extension %s has been disabled.\n", downloadURL, extension)
+ return nil
+ }
+
+ commandParts := commands.ParseCommand(strings.ReplaceAll(handlerTrimmed, "{$file}", targetFile))
+ command := exec.Command(commandParts[0], commandParts[1:]...)
+ startError := command.Start()
+ if startError != nil {
+ return startError
+ }
+ } else {
+ openError := open.Run(targetFile)
+ if openError != nil {
+ return openError
+ }
+ }
+
+ return nil
+}

MODIFY
ui/window.go
ui/window.go
@@ -5,17 +5,15 @@ import (
"fmt"
"log"
"os"
- "os/exec"
"path/filepath"
"strings"
- "sync"
"time"
"unicode"
"github.com/mattn/go-runewidth"
"github.com/mdp/qrterminal/v3"
- "github.com/skratchdot/open-golang/open"
+ "github.com/Bios-Marcel/cordless/fileopen"
"github.com/Bios-Marcel/cordless/util/files"
"github.com/Bios-Marcel/cordless/util/fuzzy"
"github.com/Bios-Marcel/cordless/util/text"
@@ -98,8 +96,6 @@ type Window struct {
bareChat bool
activeView ActiveView
-
- cacheClearLock *sync.Mutex
}
type ActiveView bool
@@ -118,7 +114,6 @@ func NewWindow(doRestart chan bool, app *tview.Application, session *discordgo.S
activeView: Guilds,
extensionEngines: []scripting.Engine{js.New()},
messageLoader: discordutil.CreateMessageLoader(session),
- cacheClearLock: &sync.Mutex{},
}
if config.Current.DesktopNotificationsUserInactivityThreshold > 0 {
@@ -370,11 +365,6 @@ func NewWindow(doRestart chan bool, app *tview.Application, session *discordgo.S
}
if shortcuts.ViewSelectedMessageImages.Equals(event) {
- links := make([]string, 0, len(message.Attachments))
- for _, file := range message.Attachments {
- links = append(links, file.URL)
- }
-
var targetFolder string
if config.Current.FileOpenSaveFilesPermanently {
@@ -394,44 +384,27 @@ func NewWindow(doRestart chan bool, app *tview.Application, session *discordgo.S
window.ShowCustomErrorDialog("Couldn't open file", "Can't create cache subdirectory.")
return nil
}
-
- //If permanent saving isn't disabled, we clear files older
- //than one month whenever something is opened. Since this
- //will happen in a background thread, it won't cause
- //application blocking.
- if !config.Current.FileOpenSaveFilesPermanently {
- defer func() {
- go func() {
- window.cacheClearLock.Lock()
- now := time.Now().Hour()
- twoWeeks := 24 * 14
- filepath.Walk(targetFolder, func(path string, f os.FileInfo, err error) error {
- if now-f.ModTime().Hour() >= twoWeeks {
- removeError := os.Remove(path)
- if removeError != nil {
- log.Printf("Couldn't remove file %s from cache.\n", removeError)
- }
- }
-
- return nil
- })
- defer window.cacheClearLock.Unlock()
- }()
- }()
- }
}
}
if targetFolder == "" {
window.ShowCustomErrorDialog("Couldn't open file", "Can't find cache directory.")
- }
-
- for _, link := range links {
- openError := window.openFile(targetFolder, link)
+ } else {
+ for _, file := range message.Attachments {
+ openError := fileopen.OpenFile(targetFolder, file.ID, file.URL)
if openError != nil {
window.ShowCustomErrorDialog("Couldn't open file", openError.Error())
}
}
+ }
+
+ //If permanent saving isn't disabled, we clear files older
+ //than one month whenever something is opened. Since this
+ //will happen in a background thread, it won't cause
+ //application blocking.
+ if !config.Current.FileOpenSaveFilesPermanently && targetFolder != "" {
+ fileopen.LaunchCacheCleaner(targetFolder, time.Hour*(24*14))
+ }
return nil
}
@@ -1284,40 +1257,6 @@ important changes of the last two versions officially released.
`, version.Version)
}
-func (window *Window) openFile(targetFolder, link string) error {
- targetFile := filepath.Join(targetFolder, filepath.Base(link))
- downloadError := files.DownloadFileOrAccessCache(targetFile, link)
- if downloadError != nil {
- return downloadError
- }
-
- extension := strings.TrimPrefix(filepath.Ext(targetFile), ".")
- handler, handlerSet := config.Current.FileOpenHandlers[extension]
- if handlerSet {
- handlerTrimmed := strings.TrimSpace(handler)
- //Empty means to not open files with the given extension.
- if handlerTrimmed == "" {
- log.Printf("skip opening link %s, as the extension %s has been disabled.\n", link, extension)
- return nil
- }
-
- commandParts := commands.ParseCommand(strings.ReplaceAll(handlerTrimmed, "{$file}", targetFile))
- command := exec.Command(commandParts[0], commandParts[1:]...)
- startError := command.Start()
- if startError != nil {
- return startError
- }
- } else {
- log.Println("Attempting to open file: " + targetFile)
- openError := open.Run(targetFile)
- if openError != nil {
- return openError
- }
- }
-
- return nil
-}
-
// initExtensionEngine injections necessary functions into the engine.
// those functions can be called by each script inside of an engine.
func (window *Window) initExtensionEngine(engine scripting.Engine) error {

More improvements to fileopening

* Add safe-guard to avoid nuking a whole hard-drive due to a potential bug
* save files with their attachment UUID instead of filename, to avoid
clashes
* move main-code of the feature into a new package (file)