commit 20555c23a44bbfe4294cabc5ef8f5386c31f3db7 Author: Daniil Tsivinsky Date: Fri Sep 12 16:18:15 2025 +0300 initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0c62ee5 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module downloads-manager + +go 1.24.6 diff --git a/main.go b/main.go new file mode 100644 index 0000000..3549c5d --- /dev/null +++ b/main.go @@ -0,0 +1,190 @@ +package main + +import ( + "flag" + "fmt" + "html/template" + "io" + "log" + "net/http" + "os" + "path" + "slices" + "strings" + "time" +) + +type Entry struct { + IsDir bool + Name string + Path string + ModifiedAt string +} + +func serveFile(w http.ResponseWriter, r *http.Request, filePath string) error { + imgFormats := []string{".png", ".jpeg", ".jpg", ".bmp", ".svg", ".gif", ".avif"} + videoFormats := []string{".mp4", ".webm"} + + for _, format := range imgFormats { + if strings.HasSuffix(filePath, format) { + http.ServeFile(w, r, filePath) + return nil + } + } + + for _, format := range videoFormats { + if strings.HasSuffix(filePath, format) { + http.ServeFile(w, r, filePath) + return nil + } + } + + if strings.HasSuffix(filePath, ".pdf") { + http.ServeFile(w, r, filePath) + return nil + } + + f, err := os.OpenFile(filePath, os.O_RDONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + return err + } + + w.Header().Set("Content-Type", "text/plain") + w.Write(data) + + return nil +} + +var ( + directory string +) + +func main() { + flag.StringVar(&directory, "dir", "./", "directory to list files from") + flag.Parse() + + tmpl := template.Must(template.ParseGlob("./views/**")) + + mux := http.NewServeMux() + + mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { + sortValue := r.URL.Query().Get("sort") + if sortValue == "" { + sortValue = "oldest" + } + + directoriesFirst := r.URL.Query().Has("directoriesFirst") + + suffix := r.URL.Path + dirPath := path.Join(directory, suffix) + + stat, err := os.Stat(dirPath) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + if !stat.IsDir() { + err = serveFile(w, r, dirPath) + if err != nil { + http.Error(w, err.Error(), 500) + } + return + } + + files, err := os.ReadDir(dirPath) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + entries := []Entry{} + for _, file := range files { + info, err := file.Info() + if err != nil { + fmt.Printf("error reading info for file %s: %v\n", file.Name(), err) + continue + } + + entries = append(entries, Entry{ + IsDir: file.IsDir(), + Name: file.Name(), + Path: path.Join(suffix, file.Name()), + ModifiedAt: info.ModTime().Format(time.DateTime), + }) + } + + slices.SortFunc(entries, func(a, b Entry) int { + if directoriesFirst { + if a.IsDir && !b.IsDir { + return -1 + } + if b.IsDir && !a.IsDir { + return 1 + } + } + + t1, err := time.Parse(time.DateTime, a.ModifiedAt) + t2, err := time.Parse(time.DateTime, b.ModifiedAt) + if err != nil { + return 0 + } + + switch sortValue { + case "oldest": + return t1.Compare(t2) + case "newest": + return t2.Compare(t1) + default: + return 0 + } + }) + + tmpl.ExecuteTemplate(w, "index.html", struct { + Entries []Entry + }{entries}) + }) + + mux.HandleFunc("POST /delete", func(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), 500) + return + } + + files := []string{} + for k, v := range r.Form { + if k == "files" { + files = v + } + } + + for _, filePath := range files { + fullPath := path.Join(directory, filePath) + + stat, err := os.Stat(fullPath) + if err != nil { + continue + } + + if stat.IsDir() { + continue // for now + } + + if err := os.Remove(fullPath); err != nil { + fmt.Printf("couldn't delete file %s: %v\n", fullPath, err) + } + } + + http.Redirect(w, r, "/", http.StatusFound) + }) + + if err := http.ListenAndServe(":5000", mux); err != nil { + log.Fatalf("failed to start http server: %v\n", err) + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..68ece21 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# downloads-manager + +Main feature is to list files and directories inside ~/Downloads folder and show each one's last modification date, user can select files to delete diff --git a/views/index.html b/views/index.html new file mode 100644 index 0000000..be287ed --- /dev/null +++ b/views/index.html @@ -0,0 +1,160 @@ + + + + + + Downloads Manager + + + + +
+

Downloads Manager

+ +
+
+ +
+
+ +
+
+ +
+
    + {{range .Entries}} +
  • +
    + + {{.Name}} +
    + {{.ModifiedAt}} +
  • + {{end}} +
+ + + +
+
+ +