This commit is contained in:
Marcel Schramm 2020-09-12 15:39:30 +02:00
parent 710475bc0b
commit b704c9183b
No known key found for this signature in database
GPG Key ID: 05971054C70EEDC7
2 changed files with 164 additions and 14 deletions

View File

@ -1,11 +1,15 @@
package commandimpls
import (
"archive/zip"
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"github.com/Bios-Marcel/discordgo"
@ -19,15 +23,26 @@ const fileSendDocumentation = `[::b]NAME
file-send - send files from your local machine
[::b]SYNOPSIS
[::b]file-send <FILE_PATH>...
[::b]file-send [OPTION[]... <FILE_PATH>...
[::b]DESCRIPTION
The file-send command allows you to send multiple files to your current channel.
[::b]OPTIONS
[::b]-b, --bulk
Zips all files and sends them as a single file.
Without this option, the folder structure won't be preserved.
[::b]-r, --recursive
Allow sending folders as well
[::b]EXAMPLES
[gray]$ file-send ~/file.txt
[gray]$ file-send -r ~/folder
[gray]$ file-send -r -b ~/folder
[gray]$ file-send ~/file1.txt ~/file2.txt
[gray]$ file-send "~/file one.txt" ~/file2.txt`
[gray]$ file-send "~/file one.txt" ~/file2.txt
[gray]$ file-send -b "~/file one.txt" ~/file2.txt
[gray]$ file-send -b -r "~/file one.txt" ~/folder ~/file2.txt`
// FileSend represents the command used to send multiple files to a channel.
type FileSend struct {
@ -56,33 +71,103 @@ func (cmd *FileSend) Execute(writer io.Writer, parameters []string) {
return
}
var filteredParameters []string
//Will cause all files in folders to be upload. This counts for subfolders as well.
var recursive bool
//Puts all files into one zip.
var bulk bool
//Parse flags and sort them out for further processing.
for _, parameter := range parameters {
if parameter == "-r" || parameter == "--recursive" {
recursive = true
} else if parameter == "-b" || parameter == "--bulk" {
bulk = true
} else {
filteredParameters = append(filteredParameters, parameter)
}
}
//Assume that all leftofer parameters are paths and convert them to absolute paths.
var consumablePaths []string
for _, parameter := range filteredParameters {
resolvedPath, resolveError := files.ToAbsolutePath(parameter)
if resolveError != nil {
fmt.Fprintf(writer, "["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]Error reading file:\n\t["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]%s\n", resolveError.Error())
continue
return
}
data, readError := ioutil.ReadFile(resolvedPath)
if readError != nil {
fmt.Fprintf(writer, "["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]Error reading file:\n\t["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]%s\n", readError.Error())
continue
}
consumablePaths = append(consumablePaths, resolvedPath)
}
dataChannel := bytes.NewReader(data)
_, sendError := cmd.discord.ChannelFileSend(channel.ID, path.Base(resolvedPath), dataChannel)
if sendError != nil {
fmt.Fprintf(writer, "["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]Error sending file:\n\t["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]%s\n", sendError.Error())
//If folders are not to be included, we error if any folder is found.
if !recursive {
for _, path := range consumablePaths {
//FIXME Handle error.
stats, _ := os.Stat(path)
if stats.IsDir() {
fmt.Fprintf(writer, "["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]Directories can only be uploaded if the '-r' flag is set.\n")
break
}
}
}
if bulk {
//We read and write at the same time to save performance and memory.
zipOutput, zipInput := io.Pipe()
//While we write, we read in a background thread. We stay in
//memory, instead of going over the filesystem.
go func() {
defer zipOutput.Close()
_, sendError := cmd.discord.ChannelFileSend(channel.ID, "files.zip", zipOutput)
if sendError != nil {
fmt.Fprintf(writer, "["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]Error sending file:\n\t["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]%s\n", sendError.Error())
}
}()
zipWriter := zip.NewWriter(zipInput)
defer zipInput.Close()
defer zipWriter.Close()
for _, parameter := range consumablePaths {
zipError := files.AddToZip(zipWriter, parameter)
if zipError != nil {
log.Println(zipError.Error())
}
}
} else {
//We skip directories and flatten the folder structure.
for _, filePath := range consumablePaths {
filepath.Walk(filePath, func(file string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
data, readError := ioutil.ReadFile(file)
if readError != nil {
fmt.Fprintf(writer, "["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]Error reading file:\n\t["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]%s\n", readError.Error())
return nil
}
dataChannel := bytes.NewReader(data)
_, sendError := cmd.discord.ChannelFileSend(channel.ID, path.Base(file), dataChannel)
if sendError != nil {
fmt.Fprintf(writer, "["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]Error sending file:\n\t["+tviewutil.ColorToHex(config.GetTheme().ErrorColor)+"]%s\n", sendError.Error())
}
return nil
})
}
}
}
// Name represents the main-name of the command.
func (cmd *FileSend) Name() string {
return "file-send"
}
// Aliases represents all available aliases this command can be called with.
func (cmd *FileSend) Aliases() []string {
return []string{"filesend", "sendfile", "send-file"}
return []string{"filesend", "sendfile", "send-file", "file-upload", "upload-file"}
}
// PrintHelp prints the help for the FileSend command.

65
util/files/zip.go Normal file
View File

@ -0,0 +1,65 @@
package files
import (
"archive/zip"
"io"
"os"
"path/filepath"
"strings"
)
// AddToZip writes the given data to a zip archive using the passed writer.
// Folders are added to the zip recursively. The folder structure is fully
// preserved.
func AddToZip(zipWriter *zip.Writer, filename string) error {
info, statError := os.Stat(filename)
if os.IsNotExist(statError) || statError != nil {
return statError
}
var baseDir string
if info.IsDir() {
baseDir = filepath.Base(filename)
}
filepath.Walk(filename, func(path string, info os.FileInfo, readError error) error {
if readError != nil {
return readError
}
header, readError := zip.FileInfoHeader(info)
if readError != nil {
return readError
}
if baseDir != "" {
header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, filename))
}
if info.IsDir() {
header.Name += "/"
} else {
header.Method = zip.Deflate
}
writer, headerError := zipWriter.CreateHeader(header)
if headerError != nil {
return headerError
}
if info.IsDir() {
return nil
}
file, readError := os.Open(path)
if readError != nil {
return readError
}
defer file.Close()
_, readError = io.Copy(writer, file)
return readError
})
return nil
}