aboutsummaryrefslogtreecommitdiff
path: root/src/files.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/files.go')
-rw-r--r--src/files.go449
1 files changed, 449 insertions, 0 deletions
diff --git a/src/files.go b/src/files.go
new file mode 100644
index 0000000..831ed4d
--- /dev/null
+++ b/src/files.go
@@ -0,0 +1,449 @@
+package main
+
+import (
+ "archive/zip"
+ "bufio"
+ "io"
+ "os"
+ "fmt"
+ "html/template"
+ "net/http"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+ // "syscall"
+)
+
+type MalformedLinkError struct {
+ Link string
+ Target string
+}
+
+func (e *MalformedLinkError) Error() string { return fmt.Sprintf("%s: broken link to %s", e.Link, e.Target) }
+
+type FileNode struct {
+ URI string
+ Path string
+ IsDir bool
+ Info os.FileInfo
+ Data any
+}
+
+func (fileNode *FileNode) HTMLPath() template.HTML {
+ var htmlpath string
+ htmlpath += `<a href="/view/` + `">` + "Home" + `</a> `
+ p := strings.Split(fileNode.URI, string(os.PathSeparator))
+ for i, dir := range p {
+ if p[i] != "" {
+ htmlpath += `> <a href="/view/` + filepath.Join(p[:i+1]...) + `">` + dir + `</a> `
+ }
+ }
+ return template.HTML(htmlpath)
+}
+
+func (fileNode *FileNode) EvalSymlinks() (string, *FileNode, error) {
+ var err error
+ target, path, err := linkDeref(fileNode.Path);
+ if err != nil {
+ if os.IsNotExist(err) {
+ return "", nil, err
+ }
+ return target, nil, err
+ }
+ fileInfo, err := os.Stat(path)
+ if err != nil {
+ return target, nil, err
+ }
+ return target, &FileNode{
+ Path: path,
+ URI: strings.TrimPrefix(path, homeDir),
+ Info: fileInfo,
+ IsDir: fileInfo.IsDir(),
+ }, nil
+}
+
+func (fileNode *FileNode) IconPath() (string, error) {
+ var icon string;
+ switch fileNode.Info.Mode() & os.ModeType {
+ default: icon = "file-earmark.svg"
+ case os.ModeIrregular: icon = "question.svg"
+ case os.ModeDir: icon = "folder2.svg"
+ case os.ModeSymlink:
+ _, fileNode, err := fileNode.EvalSymlinks()
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return "", err
+ }
+ icon = "link-broken-45deg.svg"
+ } else {
+ if fileNode.IsDir {
+ icon = "folder-symlink.svg"
+ } else {
+ icon = "link-45deg.svg"
+ }
+ }
+ }
+ return filepath.Join("/static/icons/bs/files", icon), nil
+}
+
+func (fileNode *FileNode) Size() (string, error) {
+ var err error
+ if fileNode.Mode() == "l" {
+ _, fileNode, err = fileNode.EvalSymlinks()
+ if err != nil {
+ return "", nil
+ }
+ }
+ if fileNode.IsDir {
+ return "", nil
+ }
+ size := float64(fileNode.Info.Size())
+ if size < 100 {
+ return strconv.FormatFloat(size, 'f', 0, 64) + " B", nil
+ }
+ units := []string{" KB", " MB", " GB", " TB", " PB", " EB", " ZB"}
+ for i := 0; i < 7; i++ {
+ size /= 1024
+ if size < 100 {
+ return strconv.FormatFloat(size, 'f', 1, 64) + units[i], nil
+ }
+ }
+ return strconv.FormatFloat(size, 'f', 1, 64) + " YiB", nil
+}
+
+func (fileNode *FileNode) Mode() string {
+ switch fileNode.Info.Mode() & os.ModeType {
+ default: return "f"
+ case os.ModeDir: return "d"
+ case os.ModeSymlink: return "l"
+ }
+}
+
+func (fileNode *FileNode) ModDate() string {
+ t := fileNode.Info.ModTime()
+ return fmt.Sprintf("%.3s %d, %d\n", t.Month(), t.Day(), t.Year())
+}
+
+func (fileNode *FileNode) ModTime() string {
+ t := fileNode.Info.ModTime()
+ return fmt.Sprintf("%d:%d\n", t.Hour(), t.Minute())
+}
+
+
+func (fileNode *FileNode) Details() (string, error) {
+ text := "Non-Regular File"
+ switch fileNode.Info.Mode() & os.ModeType {
+
+ default:
+ if fileNode.Info.Size() == 0 {
+ return "Empty File", nil
+ }
+ file, err := os.Open(fileNode.Path)
+ if err != nil {
+ return "", err
+ }
+ buffer := make([]byte, 512)
+ _, err = file.Read(buffer)
+ if err != nil {
+ return "", err
+ }
+ contentType := http.DetectContentType(buffer)
+ if contentType == "application/octet-stream" {
+ text = "Text File"
+ } else {
+ text = "*"+contentType
+ }
+
+ case os.ModeDir:
+ text = "Folder"
+
+ case os.ModeSymlink:
+ target, _, err := fileNode.EvalSymlinks()
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return "", err
+ }
+ if len(target) > 0 {
+ text = "Broken Link to '"+target+"'"
+ } else {
+ text = "Inaccessible Link"
+ }
+ } else {
+ text = "Link to " + target
+ }
+
+ case os.ModeSocket:
+ text = "Unix Socket"
+
+ case os.ModeDevice:
+ text = "Device File"
+
+ case os.ModeNamedPipe:
+ text = "Named Pipe"
+
+ case os.ModeTemporary:
+ text = "Temporary File"
+
+ case os.ModeAppend:
+ case os.ModeExclusive:
+ case os.ModeSetuid:
+ case os.ModeSetgid:
+ case os.ModeCharDevice:
+ case os.ModeSticky:
+ case os.ModeIrregular:
+ }
+
+ return text, nil
+}
+
+
+func getDirSize(path string) (int64, error) {
+ var size int64
+ err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() {
+ size += info.Size()
+ }
+ return err
+ })
+ return size, err
+}
+
+
+func getDirList(path string, sortBy string, ascending bool, dirsFirst bool) ([]*FileNode, error) {
+ entries, err := os.ReadDir(path)
+ if err != nil {
+ return nil, err
+ }
+ files := make([]*FileNode, len(entries))
+ for i, entry := range entries {
+ filePath := filepath.Join(path, entry.Name())
+ fileURI := strings.TrimLeft(path, homeDir)
+ fileInfo, err := entry.Info()
+ if err != nil {
+ return nil, err
+ }
+ files[i] = &FileNode{
+ Path: filePath,
+ URI: fileURI,
+ IsDir: entry.IsDir(),
+ Info: fileInfo,
+ }
+ }
+
+ switch sortBy {
+ case "name": sort.SliceStable(files, func(i, j int) bool {
+ return strings.ToLower(files[i].Info.Name()) < strings.ToLower(files[j].Info.Name())
+ })
+ case "size": sort.SliceStable(files, func(i, j int) bool {
+ return files[i].Info.Size() < files[j].Info.Size()
+ })
+ case "time": sort.SliceStable(files, func(i, j int) bool {
+ return files[i].Info.ModTime().Before(files[j].Info.ModTime())
+ })
+ }
+
+ if !ascending {
+ for i, j := 0, len(files)-1; i < j; i, j = i+1, j-1 {
+ files[i], files[j] = files[j], files[i]
+ }
+ }
+
+ if dirsFirst {
+ var dirs, notDirs []*FileNode
+ for _, fileNode := range files {
+ info, err := os.Stat(fileNode.Path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ info, err = os.Lstat(fileNode.Path)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ return nil, err
+ }
+ }
+ if info.IsDir() {
+ dirs = append(dirs, fileNode)
+ } else {
+ notDirs = append(notDirs, fileNode)
+ }
+ }
+ return append(dirs, notDirs...), nil
+ }
+
+ return files, nil
+}
+
+
+func addToZip(source string, writer *zip.Writer) error {
+ return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ header, err := zip.FileInfoHeader(info)
+ if err != nil {
+ return err
+ }
+ header.Method = zip.Deflate
+ header.Name, err = filepath.Rel(filepath.Dir(source), path)
+ if err != nil {
+ return err
+ }
+ if info.IsDir() {
+ header.Name += "/"
+ }
+ headerWriter, err := writer.CreateHeader(header)
+ if err != nil {
+ return err
+ }
+ if !info.Mode().IsRegular() {
+ return nil
+ }
+ f, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ _, err = io.Copy(headerWriter, f)
+ return err
+ })
+}
+
+
+func readBuffer(path string) ([]string, error) {
+ buff, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0600)
+ if err != nil {
+ return nil, err
+ }
+ defer buff.Close()
+
+ var buffer []string
+ scanner := bufio.NewScanner(buff)
+ for scanner.Scan() {
+ buffer = append(buffer, scanner.Text())
+ }
+ return buffer, nil
+}
+
+
+func fileExists(path string) (bool, error) {
+ _, err := os.Lstat(path)
+ if err == nil {
+ return true, nil
+ }
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+}
+
+
+func copyFile(src, dst string) error {
+ fin, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer fin.Close()
+
+ fout, err := os.Create(dst)
+ if err != nil {
+ return err
+ }
+ defer fout.Close()
+
+ _, err = io.Copy(fout, fin)
+ if err != nil {
+ return err
+ }
+ fin.Close()
+
+ return nil
+}
+
+
+func copyTo(src, dstDir string) error {
+ info, err := os.Lstat(src)
+ if err != nil {
+ return err
+ }
+ dst := filepath.Join(dstDir, info.Name())
+
+ fmt.Printf("Copying %s to %s\n", src, dstDir)
+ switch info.Mode() & os.ModeType {
+ case os.ModeDir:
+ if err := os.MkdirAll(dst, 0755); err != nil {
+ return err
+ }
+ if err := copyDir(src, dst); err != nil {
+ return err
+ }
+ case os.ModeSymlink:
+ if err := copySymlink(src, dst); err != nil {
+ return err
+ }
+ default:
+ if err := copyFile(src, dst); err != nil {
+ return err
+ }
+ }
+ fmt.Println("Finished Copying.\n\n")
+
+ if info.Mode()&os.ModeSymlink == 0 {
+ return os.Chmod(dst, info.Mode())
+ }
+ return nil
+}
+
+
+func linkDeref(link string) (string, string, error) {
+ target, err := os.Readlink(link)
+ if err != nil {
+ return "", "", err
+ }
+ path := target
+ if filepath.IsAbs(target) {
+ if !strings.HasPrefix(path, homeDir) {
+ return target, "", os.ErrNotExist
+ }
+ target = strings.TrimPrefix(target, homeDir)
+ } else {
+ path = filepath.Join(filepath.Dir(link), path)
+ if !strings.HasPrefix(path, homeDir) {
+ return target, "", os.ErrNotExist
+ }
+ }
+ return target, path, nil
+}
+
+
+func readData(key string) ([]byte, error) {
+ data, err := os.ReadFile(filepath.Join(dataDir, key))
+ if err != nil {
+ return nil, err
+ }
+ return data, nil
+}
+
+
+func writeData(key string, data []byte) error {
+ return os.WriteFile(filepath.Join(dataDir, key), data, 644)
+}
+
+
+var dataDir string
+
+func init() {
+ userHome, err := os.UserHomeDir()
+ if err != nil {
+ panic(err)
+ }
+ dataDir = filepath.Join(userHome, ".local/share/cloud-maker")
+ if err = os.MkdirAll(dataDir, 0755); err != nil {
+ panic(err)
+ }
+}