diff --git a/Gopkg.lock b/Gopkg.lock index 90d43ae..7ae85b4 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -41,6 +41,14 @@ pruneopts = "UT" revision = "05fccaae8fc423476d98fd4c3e4699ba0fbbde48" +[[projects]] + digest = "1:7413525ee648f20b4181be7fe8103d0cb98be9e141926a03ee082dc207061e4e" + name = "github.com/phayes/freeport" + packages = ["."] + pruneopts = "UT" + revision = "b8543db493a5ed890c5499e935e2cad7504f3a04" + version = "1.0.2" + [[projects]] digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" name = "github.com/pkg/errors" @@ -52,6 +60,9 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - input-imports = ["github.com/gobuffalo/packr"] + input-imports = [ + "github.com/gobuffalo/packr", + "github.com/phayes/freeport", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index f0d94d8..74e1ae0 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -32,3 +32,7 @@ [[constraint]] name = "github.com/gobuffalo/packr" version = "1.15.1" + +[[constraint]] + name = "github.com/phayes/freeport" + version = "1.0.2" diff --git a/README.md b/README.md index c5dfb1f..7bfb7e6 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,11 @@ npm run package 4. Finish the release: `git flow release finish X.Y.Z` 5. Push to the repository: `git push --all origin && git push --tags` +## Tasks + +- [ ] Auto-complete the directory path +- [ ] Improve the treemap display + ## License DirStat is licensed under the GNU General Public License. diff --git a/assets/js/dirstat.js b/assets/js/dirstat.js index 1d50e2b..49e7f3a 100644 --- a/assets/js/dirstat.js +++ b/assets/js/dirstat.js @@ -11,6 +11,8 @@ var app = new Vue({ data: { // Path to the directory to scan dir: '', + // Indicate if the directory stats must also contain files stats + includeFiles: 0, // Scan request currently processing processing: false, // Request error @@ -20,7 +22,7 @@ var app = new Vue({ // Tree map graph: null, // Tree map sum mode - mode: 'count' + mode: 'size' }, // Computed properties computed: { @@ -36,6 +38,10 @@ var app = new Vue({ // Available modes modes: function () { return ['count', 'size', 'depth']; + }, + // Indicate if the directory path is a root path + isRootPath: function () { + return this.dir && this.dir.match(/^([A-Z]:)?[\\\/]?[^\\\/]*[\\\/]?$/) } }, // Methods @@ -44,7 +50,7 @@ var app = new Vue({ scan: function () { this.error = null; this.processing = true; - this.$http.get('stat?path=' + this.dir).then(function (data) { + this.$http.get('stat?path=' + this.dir + '&files=' + this.includeFiles).then(function (data) { var entry = data.body; this.path = [entry]; document.getElementById('treemap-container').innerHTML = ''; @@ -71,9 +77,13 @@ var app = new Vue({ }, // Map entry stats to a string mapStats: function (d) { - return 'Size: ' + Math.round(d.size / (1024 * 1024)) + ' MB
' - + 'Count: ' + d.count + ' entries
' - + 'Depth: ' + d.depth + var desc = 'Type: ' + d.type + '
' + + 'Size: ' + Math.round(d.size / (1024 * 1024)) + ' MB
'; + if (d.type === "Directory") { + desc += 'Count: ' + d.count + ' entries
' + + 'Depth: ' + d.depth + } + return desc; }, // Render the tree map renderTreemap: function () { diff --git a/assets/sass/dirstat.scss b/assets/sass/dirstat.scss index 7ebe979..f896d91 100644 --- a/assets/sass/dirstat.scss +++ b/assets/sass/dirstat.scss @@ -1,6 +1,3 @@ -/* - * dirstat.scss - */ @import '../../node_modules/bootstrap/dist/css/bootstrap.min.css'; [v-cloak] { @@ -17,3 +14,12 @@ body { padding-top: 0.4rem !important; padding-bottom: 0.4rem !important; } + +.alert { + &.alert-warning { + border-color: darken(#ffeeba, 20%); + } + &.alert-danger { + border-color: darken(#f5c6cb, 15%); + } +} diff --git a/dirstat.go b/dirstat.go index c24d5d8..d9ad343 100644 --- a/dirstat.go +++ b/dirstat.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "github.com/gobuffalo/packr" + "github.com/phayes/freeport" "io/ioutil" "log" "net/http" @@ -17,11 +18,19 @@ import ( func main() { // CLI flags - portFlag := flag.Int("port", 8080, "HTTP server port") + portFlag := flag.Int("port", 0, "HTTP server port") flag.Parse() // HTTP server address port := *portFlag + if port == 0 { + // No port provided: find a free one + if freePort, err := freeport.GetFreePort(); err != nil { + log.Fatalf("Failed to start DirStat HTTP server: unable to find a free port (%s)", err) + } else { + port = freePort + } + } addr := fmt.Sprintf("127.0.0.1:%d", port) url := fmt.Sprintf("http://%s", addr) @@ -62,15 +71,16 @@ func main() { // Handle GET /stat // Calculate directory statistics and return them as JSON func statHandler(res http.ResponseWriter, req *http.Request) { - // Get path query param + // Query parameters path := req.URL.Query().Get("path") + includeFiles := req.URL.Query().Get("files") == "1" if path == "" { sendError(res, http.StatusBadRequest, "The path can't be empty") return } // else // Calculate statistics for the given directory path log.Printf("Scanning directory '%s'...", path) - if stat, err := scanDir(path); err != nil { // Scan failed + if stat, err := scanDir(path, includeFiles); err != nil { // Scan failed sendError(res, http.StatusBadRequest, "Failed scanning directory ("+err.Error()+")") } else if output, err := json.Marshal(stat); err != nil { // Marshalling failed sendError(res, http.StatusInternalServerError, "Failed encoding statistics to JSON ("+err.Error()+")") @@ -107,33 +117,40 @@ type ErrorMessage struct { // // Directory statistics structure. -type DirStat struct { - Path string `json:"path"` - Name string `json:"name"` - Count int `json:"count"` - Size int64 `json:"size"` - Depth int `json:"depth"` - Children []DirStat `json:"children"` +type EntryStat struct { + Path string `json:"path"` + Name string `json:"name"` + Type string `json:"type"` + Count int `json:"count"` + Size int64 `json:"size"` + Depth int `json:"depth"` + Children []EntryStat `json:"children"` } -// Create a new directory statistics object +// Create a new entry statistics object for a directory // from a directory path and set default values. -func NewDirStat(path string) *DirStat { - return &DirStat{Path: path, Name: fp.Base(path), Count: 0, Size: 0, - Depth: 0, Children: []DirStat{}} +func NewDirectoryStat(path string) *EntryStat { + return &EntryStat{Path: path, Name: fp.Base(path), Type: "Directory", + Count: 0, Size: 0, Depth: 0, Children: []EntryStat{}} +} + +// Create a new entry statistics object for a file. +func NewFileStat(path string, name string, size int64) *EntryStat { + return &EntryStat{Name: name, Path: path, Type: "File", Size: size, + Count: 0, Depth: 0, Children: []EntryStat{}} } // Append a child directory statistics object // to the current directory statistics. -func (stat *DirStat) append(childStat DirStat) { +func (stat *EntryStat) append(childStat EntryStat) { stat.Children = append(stat.Children, childStat) stat.Size += childStat.Size stat.Count += childStat.Count } // Calculate directory statistics recursively. -func scanDir(path string) (stat *DirStat, err error) { - stat = NewDirStat(path) +func scanDir(path string, includeFiles bool) (stat *EntryStat, err error) { + stat = NewDirectoryStat(path) files, err := ioutil.ReadDir(path) if err != nil { msg := fmt.Sprintf("Failed reading directory '%s'", path) @@ -146,13 +163,18 @@ func scanDir(path string) (stat *DirStat, err error) { childPath := fp.Join(path, file.Name()) if file.IsDir() { // Recursive call for directories - if childStat, err := scanDir(childPath); err == nil { + if childStat, err := scanDir(childPath, includeFiles); err == nil { // Error is ignored stat.append(*childStat) } } else if file.Mode().IsRegular() { - // Append file size - stat.Size += file.Size() + if includeFiles { + // Append whole file stats + stat.append(*NewFileStat(childPath, file.Name(), file.Size())) + } else { + // Only append file size + stat.Size += file.Size() + } } } // Compute directory depth diff --git a/package.json b/package.json index eebe898..452ce9c 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "dir-stat", - "version": "0.2.0", + "version": "0.3.0", "description": "Simple directory statistics", "scripts": { "build:assets": "gulp", "build": "npm run build:assets && packr build -i -o dirstat.exe .", "ensure": "npm install && dep ensure", - "start": "dirstat", + "start": "npm run build && dirstat", "package": "powershell.exe ./scripts/package.ps1" }, "repository": { diff --git a/web/index.html b/web/index.html index d0fc61c..a63300b 100644 --- a/web/index.html +++ b/web/index.html @@ -20,18 +20,34 @@

🗃 DirStat

- -
+ +
+ + +
+
+ +
+ +
+
- -
{{ error }}
+ +
{{ error }}
+ +
+ Warning! You have chosen to scan a root folder, it may take a long time... +
🗃 DirStat
-
- -
+ -
- -
+ -
-
-
- -
- +
+
+
+