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)
This commit is contained in:
parent
4db341e20b
commit
c0152662f0
|
@ -0,0 +1,82 @@
|
|||
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
|
||||
}
|
89
ui/window.go
89
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,43 +384,26 @@ 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.")
|
||||
} 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, link := range links {
|
||||
openError := window.openFile(targetFolder, link)
|
||||
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 {
|
||||
|
|
Loading…
Reference in New Issue