diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml new file mode 100644 index 0000000..f50f4a4 --- /dev/null +++ b/.github/workflows/releases.yml @@ -0,0 +1,34 @@ +name: Releases + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build + run: chmod +x build.sh && ./build.sh + + - name: Package + run: | + mkdir dist && tar -czvf dist/sfs-linux-amd64.tar.gz sfs + mv sfs.exe dist/sfs-windows-amd64.exe + + - name: Upload Release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: dist/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1458d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +sfs* +.idea/ diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..a562557 --- /dev/null +++ b/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +flags="-s -w" +export CGO_ENABLED=0 +go build -ldflags="$flags" +GOOS=windows GOARCH=amd64 go build -ldflags="$flags" diff --git a/main.go b/main.go index a7b7944..4732fd7 100644 --- a/main.go +++ b/main.go @@ -3,92 +3,97 @@ package main import ( "flag" "fmt" - "io/fs" "net/http" "os" "path" + "sort" "strconv" "strings" ) var ( - port *string - root *string + Port *string + Root *string ) -type InfoShow struct { - Name string - Time string - Size string - Path string -} - -type FileInfo struct { - Path string - Obj *os.File - Info fs.FileInfo -} - func main() { - port = flag.String("port", "8080", "server port") - root = flag.String("path", "./", "server root path") + Port = flag.String("port", "8080", "server port") + Root = flag.String("path", "./", "server root path") flag.Parse() - fmt.Println("start HTTP server @ 0.0.0.0:" + *port + "\n" + "load storage @ " + *root) + fmt.Printf("start HTTP server @ 0.0.0.0:%s\nload storage @ %s", *Port, *Root) - http.HandleFunc("/", FileServer) - http.ListenAndServe(":"+*port, nil) + http.HandleFunc("/", fileServer) + err := http.ListenAndServe(":"+*Port, nil) + if err != nil { + fmt.Printf("start error: %v", err) + os.Exit(1) + } } -func FileServer(w http.ResponseWriter, req *http.Request) { - var file FileInfo - var info InfoShow - - file.Path = path.Join(*root, req.URL.Path) - - var err error - file.Obj, err = os.Open(file.Path) +func fileServer(w http.ResponseWriter, req *http.Request) { + fPath := path.Join(*Root, req.URL.Path) + fObj, err := os.Open(fPath) if err != nil { http.NotFound(w, req) return } - defer file.Obj.Close() - - file.Info, _ = file.Obj.Stat() - - if file.Info.IsDir() { - fileLists, _ := file.Obj.Readdir(-1) - - w.Write([]byte("
../" + "\n"))
-		for _, f := range fileLists {
-			var list string
-
-			info.Time = f.ModTime().Format("2006-01-02 15:04:05")
-			info.Name = f.Name()
-			info.Path = path.Join(req.URL.Path, info.Name)
-
-			lenName := max(45-len(info.Name), 4)
+	defer fObj.Close()
+	fInfo, _ := fObj.Stat()
+
+	if fInfo.IsDir() {
+		fList, _ := fObj.Readdir(-1)
+		sort.SliceStable(fList, func(i, j int) bool {
+			if fList[i].IsDir() == fList[j].IsDir() {
+				return fList[i].Name() < fList[j].Name()
+			}
+			return fList[i].IsDir() && !fList[j].IsDir()
+		})
+
+		var oLastIndex string
+		oIndex := req.URL.Path
+		if oIndex == "/" {
+			oLastIndex = oIndex
+		} else {
+			oLastIndex = path.Dir(oIndex)
+			oIndex += "/"
+		}
 
-			if f.IsDir() {
-				list = "" + info.Name + "/" + strings.Repeat(" ", lenName-1) + info.Time
+		head := fmt.Sprintf("

Index of %s


../\n", oIndex, oLastIndex)
+		_, _ = w.Write([]byte(head))
+
+		for _, _f := range fList {
+			var li string
+			mName := _f.Name()
+			mUrl := path.Join(req.URL.Path, mName)
+			mTime := _f.ModTime().Format("2006-01-02 15:04:05")
+			mNameLength := max(50-len(mName), 1)
+			const mSizeLength = 19
+
+			if _f.IsDir() {
+				sn := strings.Repeat(" ", mNameLength)
+				sl := strings.Repeat(" ", mSizeLength)
+				li = fmt.Sprintf("%s/%s%s%s-\n", mUrl, mName, sn, mTime, sl)
 			} else {
-				infoSize := f.Size()
-				if infoSize > 10240 {
-					infoSize >>= 10
-					info.Size = strconv.FormatInt(infoSize, 10) + "kb"
+				var mSize string
+				_size := _f.Size()
+
+				if _size > 10240 {
+					_size >>= 10
+					mSize = strconv.FormatInt(_size, 10) + "kb"
 				} else {
-					info.Size = strconv.FormatInt(infoSize, 10)
+					mSize = strconv.FormatInt(_size, 10)
 				}
 
-				lenSize := max(15-len(info.Size), 4)
-
-				list = "" + info.Name + "" + strings.Repeat(" ", lenName) + info.Time + strings.Repeat(" ", lenSize) + info.Size
+				sn := strings.Repeat(" ", mNameLength+1)
+				sl := strings.Repeat(" ", max(mSizeLength-len(mSize), 1)+1)
+				li = fmt.Sprintf("%s%s%s%s%s\n", mUrl, mName, sn, mTime, sl, mSize)
 			}
 
-			w.Write([]byte(list + "\n"))
+			_, _ = w.Write([]byte(li))
 		}
-		w.Write([]byte("
")) + _, _ = w.Write([]byte("

")) } else { - http.ServeContent(w, req, file.Info.Name(), file.Info.ModTime(), file.Obj) + http.ServeContent(w, req, fInfo.Name(), fInfo.ModTime(), fObj) } }