diff --git a/go.mod b/go.mod index c75644f45..f6914f986 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/chromedp/cdproto v0.0.0-20200608134039-8a80cdaf865c github.com/chromedp/chromedp v0.5.3 github.com/disintegration/imaging v1.6.0 + github.com/fvbommel/sortorder v1.0.2 github.com/go-chi/chi v4.0.2+incompatible github.com/gobuffalo/packr/v2 v2.0.2 github.com/golang-migrate/migrate/v4 v4.3.1 diff --git a/go.sum b/go.sum index 62cc20c93..03e52adfe 100644 --- a/go.sum +++ b/go.sum @@ -128,6 +128,8 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= +github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo= +github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= diff --git a/pkg/database/database.go b/pkg/database/database.go index 6d685bf8b..39b62e017 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -7,6 +7,7 @@ import ( "os" "time" + "github.com/fvbommel/sortorder" "github.com/gobuffalo/packr/v2" "github.com/golang-migrate/migrate/v4" sqlite3mig "github.com/golang-migrate/migrate/v4/database/sqlite3" @@ -225,6 +226,19 @@ func registerCustomDriver() { } } + // COLLATE NATURAL_CS - Case sensitive natural sort + err := conn.RegisterCollation("NATURAL_CS", func(s string, s2 string) int { + if sortorder.NaturalLess(s, s2) { + return -1 + } else { + return 1 + } + }) + + if err != nil { + return fmt.Errorf("Error registering natural sort collation: %s", err.Error()) + } + return nil }, }, diff --git a/pkg/models/querybuilder_movies.go b/pkg/models/querybuilder_movies.go index 85eeb5708..a111cf563 100644 --- a/pkg/models/querybuilder_movies.go +++ b/pkg/models/querybuilder_movies.go @@ -225,6 +225,12 @@ func (qb *MovieQueryBuilder) getMovieSort(findFilter *FindFilterType) string { sort = findFilter.GetSort("name") direction = findFilter.GetDirection() } + + // #943 - override name sorting to use natural sort + if sort == "name" { + return " ORDER BY " + getColumn("movies", sort) + " COLLATE NATURAL_CS " + direction + } + return getSort(sort, direction, "movies") } diff --git a/pkg/models/querybuilder_sql.go b/pkg/models/querybuilder_sql.go index ef21acb3c..58b6a6ab2 100644 --- a/pkg/models/querybuilder_sql.go +++ b/pkg/models/querybuilder_sql.go @@ -173,6 +173,9 @@ func getSort(sort string, direction string, tableName string) string { if strings.Compare(sort, "name") == 0 { return " ORDER BY " + colName + " COLLATE NOCASE " + direction + additional } + if strings.Compare(sort, "title") == 0 { + return " ORDER BY " + colName + " COLLATE NATURAL_CS " + direction + additional + } return " ORDER BY " + colName + " " + direction + additional } diff --git a/ui/v2.5/src/components/Changelog/versions/v050.md b/ui/v2.5/src/components/Changelog/versions/v050.md index 2150d257f..c48a121ac 100644 --- a/ui/v2.5/src/components/Changelog/versions/v050.md +++ b/ui/v2.5/src/components/Changelog/versions/v050.md @@ -1,3 +1,4 @@ ### 🎨 Improvements +* Use natural sort for titles and movie names. * Support optional preview and sprite generation during scanning. * Support configurable number of threads for scanning and generation. diff --git a/vendor/github.com/fvbommel/sortorder/.gitignore b/vendor/github.com/fvbommel/sortorder/.gitignore new file mode 100644 index 000000000..c021733e2 --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/.gitignore @@ -0,0 +1,19 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so +# Folders +_obj +_test +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* +_testmain.go +*.exe +*.test +*.prof diff --git a/vendor/github.com/fvbommel/sortorder/LICENSE b/vendor/github.com/fvbommel/sortorder/LICENSE new file mode 100644 index 000000000..5c695fb59 --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/LICENSE @@ -0,0 +1,17 @@ +The MIT License (MIT) +Copyright (c) 2015 Frits van Bommel +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/fvbommel/sortorder/README.md b/vendor/github.com/fvbommel/sortorder/README.md new file mode 100644 index 000000000..7ebcab1d1 --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/README.md @@ -0,0 +1,5 @@ +# sortorder [![PkgGoDev](https://pkg.go.dev/badge/github.com/fvbommel/sortorder)](https://pkg.go.dev/github.com/fvbommel/sortorder) + + import "github.com/fvbommel/sortorder" + +Sort orders and comparison functions. diff --git a/vendor/github.com/fvbommel/sortorder/doc.go b/vendor/github.com/fvbommel/sortorder/doc.go new file mode 100644 index 000000000..a7dd9585d --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/doc.go @@ -0,0 +1,5 @@ +// Package sortorder implements sort orders and comparison functions. +// +// Currently, it only implements so-called "natural order", where integers +// embedded in strings are compared by value. +package sortorder // import "github.com/fvbommel/sortorder" diff --git a/vendor/github.com/fvbommel/sortorder/go.mod b/vendor/github.com/fvbommel/sortorder/go.mod new file mode 100644 index 000000000..57c8175e3 --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/go.mod @@ -0,0 +1,3 @@ +module github.com/fvbommel/sortorder + +go 1.13 diff --git a/vendor/github.com/fvbommel/sortorder/go.sum b/vendor/github.com/fvbommel/sortorder/go.sum new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/github.com/fvbommel/sortorder/natsort.go b/vendor/github.com/fvbommel/sortorder/natsort.go new file mode 100644 index 000000000..66a52c712 --- /dev/null +++ b/vendor/github.com/fvbommel/sortorder/natsort.go @@ -0,0 +1,76 @@ +package sortorder + +// Natural implements sort.Interface to sort strings in natural order. This +// means that e.g. "abc2" < "abc12". +// +// Non-digit sequences and numbers are compared separately. The former are +// compared bytewise, while the latter are compared numerically (except that +// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") +// +// Limitation: only ASCII digits (0-9) are considered. +type Natural []string + +func (n Natural) Len() int { return len(n) } +func (n Natural) Swap(i, j int) { n[i], n[j] = n[j], n[i] } +func (n Natural) Less(i, j int) bool { return NaturalLess(n[i], n[j]) } + +func isdigit(b byte) bool { return '0' <= b && b <= '9' } + +// NaturalLess compares two strings using natural ordering. This means that e.g. +// "abc2" < "abc12". +// +// Non-digit sequences and numbers are compared separately. The former are +// compared bytewise, while the latter are compared numerically (except that +// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") +// +// Limitation: only ASCII digits (0-9) are considered. +func NaturalLess(str1, str2 string) bool { + idx1, idx2 := 0, 0 + for idx1 < len(str1) && idx2 < len(str2) { + c1, c2 := str1[idx1], str2[idx2] + dig1, dig2 := isdigit(c1), isdigit(c2) + switch { + case dig1 != dig2: // Digits before other characters. + return dig1 // True if LHS is a digit, false if the RHS is one. + case !dig1: // && !dig2, because dig1 == dig2 + // UTF-8 compares bytewise-lexicographically, no need to decode + // codepoints. + if c1 != c2 { + return c1 < c2 + } + idx1++ + idx2++ + default: // Digits + // Eat zeros. + for ; idx1 < len(str1) && str1[idx1] == '0'; idx1++ { + } + for ; idx2 < len(str2) && str2[idx2] == '0'; idx2++ { + } + // Eat all digits. + nonZero1, nonZero2 := idx1, idx2 + for ; idx1 < len(str1) && isdigit(str1[idx1]); idx1++ { + } + for ; idx2 < len(str2) && isdigit(str2[idx2]); idx2++ { + } + // If lengths of numbers with non-zero prefix differ, the shorter + // one is less. + if len1, len2 := idx1-nonZero1, idx2-nonZero2; len1 != len2 { + return len1 < len2 + } + // If they're equal, string comparison is correct. + if nr1, nr2 := str1[nonZero1:idx1], str2[nonZero2:idx2]; nr1 != nr2 { + return nr1 < nr2 + } + // Otherwise, the one with less zeros is less. + // Because everything up to the number is equal, comparing the index + // after the zeros is sufficient. + if nonZero1 != nonZero2 { + return nonZero1 < nonZero2 + } + } + // They're identical so far, so continue comparing. + } + // So far they are identical. At least one is ended. If the other continues, + // it sorts last. + return len(str1) < len(str2) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0107a33af..b73cfc031 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -100,6 +100,8 @@ github.com/davecgh/go-spew/spew github.com/disintegration/imaging # github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify +# github.com/fvbommel/sortorder v1.0.2 +github.com/fvbommel/sortorder # github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/chi github.com/go-chi/chi/middleware