From 68add78230894fe32d14e176d9236390081fe371 Mon Sep 17 00:00:00 2001 From: pyed Date: Mon, 15 Jun 2015 05:02:10 +0300 Subject: [PATCH] Implement sorting functionality for "Browse" --- config/setup/browse.go | 30 +++++- middleware/browse/browse.go | 157 ++++++++++++++++++++++--------- middleware/browse/browse_test.go | 96 +++++++++++++++++++ 3 files changed, 238 insertions(+), 45 deletions(-) create mode 100644 middleware/browse/browse_test.go diff --git a/config/setup/browse.go b/config/setup/browse.go index ca1ad40e..bf5a3fe9 100644 --- a/config/setup/browse.go +++ b/config/setup/browse.go @@ -202,9 +202,33 @@ th {
- - - + + + {{range .Items}} diff --git a/middleware/browse/browse.go b/middleware/browse/browse.go index ea2ff52c..c0090acd 100644 --- a/middleware/browse/browse.go +++ b/middleware/browse/browse.go @@ -4,11 +4,13 @@ package browse import ( "bytes" + "errors" "html/template" "net/http" "net/url" "os" "path" + "sort" "strings" "time" @@ -43,6 +45,12 @@ type Listing struct { // The items (files and folders) in the path Items []FileInfo + + // Which sorting order is used + Sort string + + // And which order + Order string } // FileInfo is the info about a particular file or directory @@ -55,6 +63,61 @@ type FileInfo struct { Mode os.FileMode } +// Implement sorting for Listing +type byName Listing +type bySize Listing +type byTime Listing + +// By Name +func (l byName) Len() int { return len(l.Items) } +func (l byName) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] } + +// Treat upper and lower case equally +func (l byName) Less(i, j int) bool { + return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) +} + +// By Size +func (l bySize) Len() int { return len(l.Items) } +func (l bySize) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] } +func (l bySize) Less(i, j int) bool { return l.Items[i].Size < l.Items[j].Size } + +// By Time +func (l byTime) Len() int { return len(l.Items) } +func (l byTime) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] } +func (l byTime) Less(i, j int) bool { return l.Items[i].ModTime.Unix() < l.Items[j].ModTime.Unix() } + +// Add sorting method to "Listing" +// it will apply what's in ".Sort" and ".Order" +func (l Listing) applySort() { + // Check '.Order' to know how to sort + if l.Order == "desc" { + switch l.Sort { + case "name": + sort.Sort(sort.Reverse(byName(l))) + case "size": + sort.Sort(sort.Reverse(bySize(l))) + case "time": + sort.Sort(sort.Reverse(byTime(l))) + default: + // If not one of the above, do nothing + return + } + } else { // If we had more Orderings we could add them here + switch l.Sort { + case "name": + sort.Sort(byName(l)) + case "size": + sort.Sort(bySize(l)) + case "time": + sort.Sort(byTime(l)) + default: + // If not one of the above, do nothing + return + } + } +} + // HumanSize returns the size of the file as a human-readable string. func (fi FileInfo) HumanSize() string { return humanize.Bytes(uint64(fi.Size)) @@ -72,6 +135,42 @@ var IndexPages = []string{ "default.htm", } +func directoryListing(files []os.FileInfo, urlPath string, canGoUp bool) (Listing, error) { + var fileinfos []FileInfo + for _, f := range files { + name := f.Name() + + // Directory is not browsable if it contains index file + for _, indexName := range IndexPages { + if name == indexName { + return Listing{}, errors.New("Directory contains index file, not browsable!") + } + } + + if f.IsDir() { + name += "/" + } + + url := url.URL{Path: name} + + fileinfos = append(fileinfos, FileInfo{ + IsDir: f.IsDir(), + Name: f.Name(), + Size: f.Size(), + URL: url.String(), + ModTime: f.ModTime(), + Mode: f.Mode(), + }) + } + + return Listing{ + Name: path.Base(urlPath), + Path: urlPath, + CanGoUp: canGoUp, + Items: fileinfos, + }, nil +} + // ServeHTTP implements the middleware.Handler interface. func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { filename := b.Root + r.URL.Path @@ -113,42 +212,6 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { return http.StatusForbidden, err } - // Assemble listing of directory contents - var fileinfos []FileInfo - var abort bool // we bail early if we find an index file - for _, f := range files { - name := f.Name() - - // Directory is not browseable if it contains index file - for _, indexName := range IndexPages { - if name == indexName { - abort = true - break - } - } - if abort { - break - } - - if f.IsDir() { - name += "/" - } - url := url.URL{Path: name} - - fileinfos = append(fileinfos, FileInfo{ - IsDir: f.IsDir(), - Name: f.Name(), - Size: f.Size(), - URL: url.String(), - ModTime: f.ModTime(), - Mode: f.Mode(), - }) - } - if abort { - // this dir has an index file, so not browsable - continue - } - // Determine if user can browse up another folder var canGoUp bool curPath := strings.TrimSuffix(r.URL.Path, "/") @@ -158,14 +221,24 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { break } } - - listing := Listing{ - Name: path.Base(r.URL.Path), - Path: r.URL.Path, - CanGoUp: canGoUp, - Items: fileinfos, + // Assemble listing of directory contents + listing, err := directoryListing(files, r.URL.Path, canGoUp) + if err != nil { // directory isn't browsable + continue } + // Get the query vales and store them in the Listing struct + listing.Sort, listing.Order = r.URL.Query().Get("sort"), r.URL.Query().Get("order") + + // If the query 'sort' is empty, default to "name" and "asc" + if listing.Sort == "" { + listing.Sort = "name" + listing.Order = "asc" + } + + // Apply the sorting + listing.applySort() + var buf bytes.Buffer err = bc.Template.Execute(&buf, listing) if err != nil { diff --git a/middleware/browse/browse_test.go b/middleware/browse/browse_test.go new file mode 100644 index 00000000..33218a19 --- /dev/null +++ b/middleware/browse/browse_test.go @@ -0,0 +1,96 @@ +package browse + +import ( + "sort" + "testing" + "time" +) + +// "sort" package has "IsSorted" function, but no "IsReversed"; +func isReversed(data sort.Interface) bool { + n := data.Len() + for i := n - 1; i > 0; i-- { + if !data.Less(i, i-1) { + return false + } + } + return true +} + +func TestSort(t *testing.T) { + // making up []fileInfo with bogus values; + // to be used to make up our "listing" + fileInfos := []FileInfo{ + { + Name: "fizz", + Size: 4, + ModTime: time.Now().AddDate(-1, 1, 0), + }, + { + Name: "buzz", + Size: 2, + ModTime: time.Now().AddDate(0, -3, 3), + }, + { + Name: "bazz", + Size: 1, + ModTime: time.Now().AddDate(0, -2, -23), + }, + { + Name: "jazz", + Size: 3, + ModTime: time.Now(), + }, + } + listing := Listing{ + Name: "foobar", + Path: "/fizz/buzz", + CanGoUp: false, + Items: fileInfos, + } + + // sort by name + listing.Sort = "name" + listing.applySort() + if !sort.IsSorted(byName(listing)) { + t.Errorf("The listing isn't name sorted: %v", listing.Items) + } + + // sort by size + listing.Sort = "size" + listing.applySort() + if !sort.IsSorted(bySize(listing)) { + t.Errorf("The listing isn't size sorted: %v", listing.Items) + } + + // sort by Time + listing.Sort = "time" + listing.applySort() + if !sort.IsSorted(byTime(listing)) { + t.Errorf("The listing isn't time sorted: %v", listing.Items) + } + + // reverse by name + listing.Sort = "name" + listing.Order = "desc" + listing.applySort() + if !isReversed(byName(listing)) { + t.Errorf("The listing isn't reversed by name: %v", listing.Items) + } + + // reverse by size + listing.Sort = "size" + listing.Order = "desc" + listing.applySort() + if !isReversed(bySize(listing)) { + t.Errorf("The listing isn't reversed by size: %v", listing.Items) + } + + // reverse by time + listing.Sort = "time" + listing.Order = "desc" + listing.applySort() + if !isReversed(byTime(listing)) { + t.Errorf("The listing isn't reversed by time: %v", listing.Items) + } +}
NameSizeModified + {{if and (eq .Sort "name") (ne .Order "desc")}} + Name↓ + {{else if and (eq .Sort "name") (ne .Order "asc")}} + Name↑ + {{else}} + Name + {{end}} + + {{if and (eq .Sort "size") (ne .Order "desc")}} + Size↓ + {{else if and (eq .Sort "size") (ne .Order "asc")}} + Size↑ + {{else}} + Size + {{end}} + + {{if and (eq .Sort "time") (ne .Order "desc")}} + Modified↓ + {{else if and (eq .Sort "time") (ne .Order "asc")}} + Modified↑ + {{else}} + Modified + {{end}} +