Skip to content

Commit

Permalink
Merge branch 'release/0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
GaelGirodon committed Oct 29, 2018
2 parents d764222 + de4cab9 commit c47369b
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 53 deletions.
13 changes: 12 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@
[[constraint]]
name = "github.com/gobuffalo/packr"
version = "1.15.1"

[[constraint]]
name = "github.com/phayes/freeport"
version = "1.0.2"
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
20 changes: 15 additions & 5 deletions assets/js/dirstat.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,7 +22,7 @@ var app = new Vue({
// Tree map
graph: null,
// Tree map sum mode
mode: 'count'
mode: 'size'
},
// Computed properties
computed: {
Expand All @@ -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
Expand All @@ -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 = '';
Expand All @@ -71,9 +77,13 @@ var app = new Vue({
},
// Map entry stats to a string
mapStats: function (d) {
return '<strong>Size:</strong> ' + Math.round(d.size / (1024 * 1024)) + ' MB<br>'
+ '<strong>Count:</strong> ' + d.count + ' entries<br>'
+ '<strong>Depth:</strong> ' + d.depth
var desc = '<strong>Type:</strong> ' + d.type + '<br>'
+ '<strong>Size:</strong> ' + Math.round(d.size / (1024 * 1024)) + ' MB<br>';
if (d.type === "Directory") {
desc += '<strong>Count:</strong> ' + d.count + ' entries<br>'
+ '<strong>Depth:</strong> ' + d.depth
}
return desc;
},
// Render the tree map
renderTreemap: function () {
Expand Down
12 changes: 9 additions & 3 deletions assets/sass/dirstat.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/*
* dirstat.scss
*/
@import '../../node_modules/bootstrap/dist/css/bootstrap.min.css';

[v-cloak] {
Expand All @@ -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%);
}
}
62 changes: 42 additions & 20 deletions dirstat.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"flag"
"fmt"
"github.com/gobuffalo/packr"
"github.com/phayes/freeport"
"io/ioutil"
"log"
"net/http"
Expand All @@ -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)

Expand Down Expand Up @@ -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()+")")
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
54 changes: 32 additions & 22 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,34 @@ <h1 class="display-5 m-0">&#x1F5C3; DirStat</h1>
</header>
<!-- Directory selection -->
<section>
<!-- Directory path input -->
<div class="d-flex">
<!-- Scan options -->
<div class="form-inline">
<!-- Directory path -->
<input class="form-control flex-grow-1" id="dir" :class="{'is-invalid': error}"
placeholder="Type here the path to the folder to analyse and click on the 'Scan' button or press Enter"
v-model="dir" :disabled="processing" @keyup.enter="scan">
<!-- Display only folders or files too -->
<div class="input-group ml-2">
<div class="input-group-prepend">
<label for="includeFiles" class="input-group-text">Display</label>
</div>
<select class="custom-select" id="includeFiles" v-model="includeFiles">
<option value="0" selected>Only folders</option>
<option value="1">Files & folders</option>
</select>
</div>
<!-- Scan button -->
<button type="button" class="btn btn-primary ml-2 px-3"
@click="scan" :disabled="processing">
&#x1F50D; Scan
</button>
</div>
<!-- Error message -->
<div class="text-danger" v-cloak v-if="error">{{ error }}</div>
<!-- Error message (invalid path) -->
<div class="alert alert-danger mt-3" v-cloak v-if="error">{{ error }}</div>
<!-- Warning message (root path) -->
<div class="alert alert-warning mt-3" v-cloak v-if="!error && processing && isRootPath">
<strong>Warning!</strong> You have chosen to scan a root folder, it may take a long time...
</div>
<!-- Progress bar -->
<div class="progress mt-3" v-cloak v-if="processing">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
Expand All @@ -41,27 +57,21 @@ <h1 class="display-5 m-0">&#x1F5C3; DirStat</h1>
<!-- Navigation controls -->
<section class="form-inline my-3" v-cloak v-show="graph">
<!-- Back to the previous level -->
<div class="form-group">
<button type="button" class="btn btn-secondary" @click="back" :disabled="path.length <= 1">🡨</button>
</div>
<button type="button" class="btn btn-secondary" @click="back" :disabled="path.length <= 1">🡨</button>
<!-- Path -->
<div class="form-group ml-2">
<ol id="path" class="breadcrumb my-0 py-2" v-cloak>
<li class="breadcrumb-item" v-for="entry in path">
<a href="#" @click="backTo(entry)">{{ entry.name }}</a>
</li>
</ol>
</div>
<ol id="path" class="breadcrumb ml-2 my-0 py-2" v-cloak>
<li class="breadcrumb-item" v-for="entry in path">
<a href="#" @click="backTo(entry)">{{ entry.name }}</a>
</li>
</ol>
<!-- Change tree map sum mode -->
<div class="form-group ml-auto">
<div class="input-group">
<div class="input-group-prepend">
<label for="mode" class="input-group-text">Mode</label>
</div>
<select class="custom-select" id="mode" v-model="mode" @change="renderTreemap">
<option v-for="m in modes" :value="m">{{ m }}</option>
</select>
<div class="input-group ml-auto">
<div class="input-group-prepend">
<label for="mode" class="input-group-text">Mode</label>
</div>
<select class="custom-select" id="mode" v-model="mode" @change="renderTreemap">
<option v-for="m in modes" :value="m">{{ m }}</option>
</select>
</div>
</section>
<!-- Tree map -->
Expand Down

0 comments on commit c47369b

Please sign in to comment.