From 227dd137155fca1de695e4703770b78c54657d80 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Fri, 18 Oct 2019 22:05:35 -0400 Subject: [PATCH 01/14] Refactored devices * A lot more information (codenames, storage, etc) * Cover sizes * Cleaner code, easier to extend in the future * Codenames now match the internal layout (class -> family + optional secondary) * Based on firmware 4.18.13737 Also see https://gist.github.com/geek1011/613b34c23f026f7c39c50ee32f5e167e and https://github.com/shermp/Kobo-UNCaGED/issues/16 --- go.mod | 2 +- kobo-info/main.go | 8 +- kobo/device.go | 353 +++++++++++++++++++++++++++++++++++++++----- kobo/device_test.go | 19 ++- 4 files changed, 337 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index 8fdb49e..ff492fa 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/geek1011/koboutils -go 1.12 +go 1.13 require github.com/spf13/pflag v1.0.3 diff --git a/kobo-info/main.go b/kobo-info/main.go index 7e1850a..4c32620 100644 --- a/kobo-info/main.go +++ b/kobo-info/main.go @@ -56,9 +56,11 @@ func main() { } if device, ok := kobo.DeviceByID(id); ok { - printkv("Device", device.Name) + printkv("Device", device.Name()) printkv("Device ID", id) - printkv("Hardware", device.Hardware) + printkv("Device Family", fmt.Sprintf("%s (%s)", device.Family(), device.CodeNames().Family())) + printkv("Codenames", device.CodeNames().String()) + printkv("Hardware", device.Hardware().String()) } else { printkv("Device", "unknown") printkv("Device ID", id) @@ -96,7 +98,7 @@ func printkv(key, value string) { } fmt.Printf(` "%s": "%s"`, strings.Replace(strings.ToLower(key), " ", "_", -1), value) } else { - fmt.Printf("%10s: %s\n", key, value) + fmt.Printf("%15s: %s\n", key, value) } } diff --git a/kobo/device.go b/kobo/device.go index b6ec7e5..5bc54db 100644 --- a/kobo/device.go +++ b/kobo/device.go @@ -1,42 +1,325 @@ package kobo -// Device represents a device. -type Device struct { - ID string - Name string - Hardware string -} - -// Devices. -var ( - DeviceTouchAB = Device{"00000000-0000-0000-0000-000000000310", "Kobo Touch A/B", "kobo3"} - DeviceTouchC = Device{"00000000-0000-0000-0000-000000000320", "Kobo Touch C", "kobo4"} - DeviceMini = Device{"00000000-0000-0000-0000-000000000340", "Kobo Mini", "kobo4"} - DeviceGlo = Device{"00000000-0000-0000-0000-000000000330", "Kobo Glo", "kobo4"} - DeviceGloHD = Device{"00000000-0000-0000-0000-000000000371", "Kobo Glo HD", "kobo6"} - DeviceTouch2 = Device{"00000000-0000-0000-0000-000000000372", "Kobo Touch 2.0", "kobo6"} - DeviceAura = Device{"00000000-0000-0000-0000-000000000360", "Kobo Aura", "kobo5"} - DeviceAuraHD = Device{"00000000-0000-0000-0000-000000000350", "Kobo Aura HD", "kobo4"} - DeviceAuraH2O = Device{"00000000-0000-0000-0000-000000000370", "Kobo Aura H2O", "kobo5"} - DeviceAuraH2OEdition2v1 = Device{"00000000-0000-0000-0000-000000000374", "Kobo Aura H2O Edition 2 v1", "kobo6"} - DeviceAuraH2OEdition2v2 = Device{"00000000-0000-0000-0000-000000000378", "Kobo Aura H2O Edition 2 v2", "kobo7"} - DeviceAuraONE = Device{"00000000-0000-0000-0000-000000000373", "Kobo Aura ONE", "kobo6"} - DeviceAuraONELimitedEdition = Device{"00000000-0000-0000-0000-000000000381", "Kobo Aura ONE Limited Edition", "kobo6"} - DeviceAuraEdition2v1 = Device{"00000000-0000-0000-0000-000000000375", "Kobo Aura Edition 2 v1", "kobo6"} - DeviceAuraEdition2v2 = Device{"00000000-0000-0000-0000-000000000379", "Kobo Aura Edition 2 v2", "kobo7"} - DeviceClaraHD = Device{"00000000-0000-0000-0000-000000000376", "Kobo Clara HD", "kobo7"} - DeviceForma = Device{"00000000-0000-0000-0000-000000000377", "Kobo Forma", "kobo7"} - DeviceForma32 = Device{"00000000-0000-0000-0000-000000000380", "Kobo Forma 32GB", "kobo7"} - DeviceLibraH2O = Device{"00000000-0000-0000-0000-000000000384", "Kobo Libra H2O", "kobo7"} - Devices = []Device{DeviceTouchAB, DeviceTouchC, DeviceMini, DeviceGlo, DeviceGloHD, DeviceTouch2, DeviceAura, DeviceAuraHD, DeviceAuraH2O, DeviceAuraH2OEdition2v1, DeviceAuraH2OEdition2v2, DeviceAuraONE, DeviceAuraONELimitedEdition, DeviceAuraEdition2v1, DeviceAuraEdition2v2, DeviceForma, DeviceForma32, DeviceLibraH2O} +import ( + "fmt" + "image" ) -// DeviceByID gets the device by the ID. -func DeviceByID(id string) (*Device, bool) { - for _, device := range Devices { - if device.ID == id { - return &device, true +// See https://gist.github.com/geek1011/613b34c23f026f7c39c50ee32f5e167e and +// https://github.com/shermp/Kobo-UNCaGED/issues/16 + +type Device int +type Hardware int +type CodeName string +type CodeNameTriplet [3]CodeName +type CoverType string + +// Devices (not including really old ones, like Kobo eReader, Wireless, Literati, and Vox). +const ( + DeviceTouchAB Device = 310 + DeviceTouchC Device = 320 + DeviceGlo Device = 330 + DeviceMini Device = 340 + DeviceAuraHD Device = 350 + DeviceAura Device = 360 + DeviceAuraH2O Device = 370 + DeviceGloHD Device = 371 + DeviceTouch2 Device = 372 + DeviceAuraONE Device = 373 + DeviceAuraH2OEdition2v1 Device = 374 + DeviceAuraEdition2v1 Device = 375 + DeviceClaraHD Device = 376 + DeviceForma Device = 377 + DeviceAuraH2OEdition2v2 Device = 378 + DeviceAuraEdition2v2 Device = 379 + DeviceForma32 Device = 380 + DeviceAuraONELimitedEdition Device = 381 + DeviceLibraH2O Device = 384 +) + +// Hardware revisions. +const ( + HardwareKobo3 Hardware = 3 + HardwareKobo4 Hardware = 4 + HardwareKobo5 Hardware = 5 + HardwareKobo6 Hardware = 6 + HardwareKobo7 Hardware = 7 +) + +// Codenames. +const ( + CodeNameNone CodeName = "" + CodeNameDesktop CodeName = "desktop" + CodeNameNickel1 CodeName = "nickel1" + CodeNameNickel2 CodeName = "nickel2" + CodeNameMerch CodeName = "merch" + CodeNameVox CodeName = "vox" + CodeNameTrilogy CodeName = "trilogy" + CodeNamePixie CodeName = "pixie" + CodeNamePika CodeName = "pika" + CodeNameDragon CodeName = "dragon" + CodeNameDahlia CodeName = "dahlia" + CodeNameAlyssum CodeName = "alyssum" + CodeNameSnow CodeName = "snow" + CodeNameNova CodeName = "nova" + CodeNameStorm CodeName = "storm" + CodeNameDaylight CodeName = "daylight" + CodeNameSuperDaylight CodeName = "superDaylight" + CodeNameFrost CodeName = "frost" + CodeNameFrost32 CodeName = "frost32" + CodeNamePhoenix CodeName = "phoenix" + CodeNameKraken CodeName = "kraken" + CodeNameStar CodeName = "star" +) + +// Cover types. +const ( + CoverTypeLibFull CoverType = "N3_LIBRARY_FULL" + CoverTypeLibList CoverType = "N3_LIBRARY_LIST" + CoverTypeLibGrid CoverType = "N3_LIBRARY_GRID" +) + +func Devices() []Device { + return []Device{DeviceTouchAB, DeviceTouchC, DeviceGlo, DeviceMini, DeviceAuraHD, DeviceAura, DeviceAuraH2O, DeviceGloHD, DeviceTouch2, DeviceAuraONE, DeviceAuraH2OEdition2v1, DeviceAuraEdition2v1, DeviceClaraHD, DeviceForma, DeviceAuraH2OEdition2v2, DeviceAuraEdition2v2, DeviceForma32, DeviceAuraONELimitedEdition, DeviceLibraH2O} +} + +func CoverTypes() []CoverType { + return []CoverType{CoverTypeLibFull, CoverTypeLibList, CoverTypeLibGrid} +} + +func DeviceByID(id string) (Device, bool) { + for _, device := range Devices() { + if device.IDString() == id { + return device, true } } - return nil, false + return 0, false +} + +func (d Device) ID() int { + return int(d) +} + +func (d Device) IDString() string { + return fmt.Sprintf("00000000-0000-0000-0000-%012d", d.ID()) +} + +func (d Device) String() string { + return d.Name() +} + +func (d Device) Name() string { + cd := d.CodeNames() + dev := cd.FamilyString() + if sec := cd.SecondaryString(); sec != "" { + dev += " " + sec + } + switch d { + case DeviceTouchAB: + dev += " A/B" + case DeviceTouchC: + dev += " C" + case DeviceAuraEdition2v1, DeviceAuraEdition2v2: + dev += " Edition 2" + } + switch d { + case DeviceAuraEdition2v1, DeviceAuraH2OEdition2v1: + dev += " v1" + case DeviceAuraEdition2v2, DeviceAuraH2OEdition2v2: + dev += " v2" + } + return dev +} + +func (d Device) Hardware() Hardware { + switch d { + case DeviceTouchAB: + return HardwareKobo3 + case DeviceTouchC, DeviceMini, DeviceGlo, DeviceAuraHD: + return HardwareKobo4 + case DeviceAura, DeviceAuraH2O: + return HardwareKobo5 + case DeviceGloHD, DeviceTouch2, DeviceAuraH2OEdition2v1, DeviceAuraONE, DeviceAuraONELimitedEdition, DeviceAuraEdition2v1: + return HardwareKobo6 + case DeviceAuraH2OEdition2v2, DeviceAuraEdition2v2, DeviceClaraHD, DeviceForma, DeviceForma32, DeviceLibraH2O: + return HardwareKobo7 + } + panic("unknown device") +} + +func (h Hardware) Hardware() int { + return int(h) +} + +func (h Hardware) String() string { + return fmt.Sprintf("kobo%d", int(h)) +} + +func (d Device) CodeNames() CodeNameTriplet { + switch d { + case DeviceTouchAB, DeviceTouchC: + return CodeNameTriplet{CodeNameTrilogy, CodeNameTrilogy, CodeNameNone} + case DeviceMini: + return CodeNameTriplet{CodeNameTrilogy, CodeNamePixie, CodeNameNone} + case DeviceTouch2: + return CodeNameTriplet{CodeNameTrilogy, CodeNamePika, CodeNameNone} + + case DeviceAuraHD: + return CodeNameTriplet{CodeNameDragon, CodeNameDragon, CodeNameNone} + case DeviceAuraH2O: + return CodeNameTriplet{CodeNameDragon, CodeNameDahlia, CodeNameNone} + case DeviceGloHD: + return CodeNameTriplet{CodeNameDragon, CodeNameAlyssum, CodeNameNone} + case DeviceAuraH2OEdition2v1, DeviceAuraH2OEdition2v2: + return CodeNameTriplet{CodeNameDragon, CodeNameSnow, CodeNameNone} + case DeviceClaraHD: + return CodeNameTriplet{CodeNameDragon, CodeNameNova, CodeNameNone} + case DeviceLibraH2O: + return CodeNameTriplet{CodeNameDragon, CodeNameStorm, CodeNameNone} + + case DeviceAuraONE: + return CodeNameTriplet{CodeNameDaylight, CodeNameDaylight, CodeNameNone} + case DeviceAuraONELimitedEdition: + return CodeNameTriplet{CodeNameDaylight, CodeNameDaylight, CodeNameSuperDaylight} + case DeviceForma: + return CodeNameTriplet{CodeNameDaylight, CodeNameFrost, CodeNameNone} + case DeviceForma32: + return CodeNameTriplet{CodeNameDaylight, CodeNameFrost, CodeNameFrost32} + + case DeviceAura: + return CodeNameTriplet{CodeNamePhoenix, CodeNamePhoenix, CodeNameNone} + case DeviceGlo: + return CodeNameTriplet{CodeNamePhoenix, CodeNameKraken, CodeNameNone} + case DeviceAuraEdition2v1, DeviceAuraEdition2v2: + return CodeNameTriplet{CodeNamePhoenix, CodeNameStar, CodeNameNone} + } + panic("unknown device") +} + +func (c CodeName) String() string { + return string(c) +} + +func (c CodeNameTriplet) String() string { + if c[2] != CodeNameNone { + return fmt.Sprintf("class=%s family=%s secondary=%s", c[0], c[1], c[2]) + } + return fmt.Sprintf("class=%s family=%s", c[0], c[1]) +} + +func (d Device) Family() string { + return d.CodeNames().FamilyString() +} + +func (c CodeNameTriplet) Class() CodeName { + return c[0] +} + +func (c CodeNameTriplet) Family() CodeName { + return c[1] +} + +func (c CodeNameTriplet) FamilyString() string { + switch c.Family() { + case CodeNameDesktop: + return "Kobo Desktop" + case CodeNameNickel1: + return "Kobo eReader" + case CodeNameNickel2: + return "Kobo Wireless eReader" + case CodeNameMerch: + return "Literati / LookBook eReader" + case CodeNameVox: + return "Kobo Vox" + case CodeNameTrilogy: + return "Kobo Touch" + case CodeNamePixie: + return "Kobo Mini" + case CodeNamePika: + return "Kobo Touch 2.0" + case CodeNameDragon: + return "Kobo Aura HD" + case CodeNameDahlia: + return "Kobo Aura H2O" + case CodeNameAlyssum: + return "Kobo Glo HD" + case CodeNameSnow: + return "Kobo Aura H2O Edition 2" + case CodeNameNova: + return "Kobo Clara HD" + case CodeNameStorm: + return "Kobo Libra H2O" + case CodeNameDaylight: + return "Kobo Aura ONE" + case CodeNameFrost: + return "Kobo Forma" + case CodeNamePhoenix: + return "Kobo Aura" + case CodeNameKraken: + return "Kobo Glo" + case CodeNameStar: + return "Kobo Aura" + } + panic("unknown family") +} + +func (c CodeNameTriplet) Secondary() CodeName { + return c[2] +} + +func (c CodeNameTriplet) SecondaryString() string { + switch c.Secondary() { + case CodeNameNone: + return "" + case CodeNameSuperDaylight: + return "Limited Edition" + case CodeNameFrost32: + return "32GB" + } + panic("unknown secondary") +} + +func (d Device) CoverSize(t CoverType) image.Point { + if t == CoverTypeLibList { + return image.Pt(60, 90) + } else if t == CoverTypeLibGrid { + return image.Pt(149, 223) + } else if t != CoverTypeLibFull { + panic("unknown cover type") + } + + switch d.CodeNames().Family() { + case CodeNameDragon, CodeNameSnow: + return image.Pt(1080, 1440) + case CodeNameDahlia: + return image.Pt(1080, 1429) + case CodeNameAlyssum, CodeNameNova: + return image.Pt(1072, 1448) + case CodeNameStorm: + return image.Pt(1264, 1680) + case CodeNameDaylight: + return image.Pt(1404, 1872) + case CodeNameFrost: + return image.Pt(1440, 1920) + case CodeNamePhoenix: + return image.Pt(758, 1014) + case CodeNameKraken, CodeNameStar: + return image.Pt(758, 1024) + default: + return image.Pt(600, 800) + } +} + +func (d Device) StorageGB() int { + switch d { + case DeviceTouchAB, DeviceTouchC, DeviceMini: + return 2 + case DeviceTouch2, DeviceAuraHD, DeviceAuraH2O, DeviceGloHD, DeviceAura, DeviceGlo, DeviceAuraEdition2v1, DeviceAuraEdition2v2: + return 4 + case DeviceAuraH2OEdition2v1, DeviceAuraH2OEdition2v2, DeviceClaraHD, DeviceLibraH2O, DeviceAuraONE, DeviceForma: + return 8 + case DeviceAuraONELimitedEdition, DeviceForma32: + return 32 + } + panic("unknown device") } diff --git a/kobo/device_test.go b/kobo/device_test.go index 38b0e6c..1d9a20b 100644 --- a/kobo/device_test.go +++ b/kobo/device_test.go @@ -1,14 +1,21 @@ package kobo -import "testing" +import ( + "fmt" + "testing" +) -func TestDeviceByID(t *testing.T) { - for _, d := range Devices { - if dd, _ := DeviceByID(d.ID); dd.ID != d.ID || dd.Hardware != d.Hardware || dd.Name != d.Name { - t.Errorf("id '%s should be device %v", d.ID, d) +func TestDeviceList(t *testing.T) { + for _, d := range Devices() { + fmt.Printf("Device %d (%s):\n Family: %s (%s)\n Hardware: %s\n IDString: %s\n Storage: %dGB\n CodeNames: %s\n Cover Types:\n", int(d), d.Name(), d.Family(), d.CodeNames().Family(), d.Hardware(), d.IDString(), d.StorageGB(), d.CodeNames()) + for _, c := range CoverTypes() { + fmt.Printf(" %s: %s\n", c, d.CoverSize(c)) } + fmt.Println() } - if d, ok := DeviceByID("asdasd"); ok || d != nil { + if d, ok := DeviceByID("asdasd"); ok || d != 0 { t.Errorf("id 'asdasd' should not return a device") } } + +// TODO: more tests From 19711ab72619f5bd7160eb30179b60258567d968 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Fri, 18 Oct 2019 22:12:06 -0400 Subject: [PATCH 02/14] Fixed N3_FULL internal name N3_LIBRARY_FULL -> N3_FULL --- kobo/device.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/kobo/device.go b/kobo/device.go index 5bc54db..1254b6a 100644 --- a/kobo/device.go +++ b/kobo/device.go @@ -74,7 +74,7 @@ const ( // Cover types. const ( - CoverTypeLibFull CoverType = "N3_LIBRARY_FULL" + CoverTypeLibFull CoverType = "N3_FULL" CoverTypeLibList CoverType = "N3_LIBRARY_LIST" CoverTypeLibGrid CoverType = "N3_LIBRARY_GRID" ) @@ -310,6 +310,14 @@ func (d Device) CoverSize(t CoverType) image.Point { } } +func (c CoverType) NickelString() string { + return string(c) +} + +func (c CoverType) String() string { + return c.NickelString() +} + func (d Device) StorageGB() int { switch d { case DeviceTouchAB, DeviceTouchC, DeviceMini: From ba8a761d376aee98ab856ccd68509f6d83495e46 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Fri, 18 Oct 2019 22:16:22 -0400 Subject: [PATCH 03/14] Added cover resizing code Based on our (@geek1011, @NiLuJe) previous work for @shermp's Kobo-UNCaGED (https://github.com/shermp/Kobo-UNCaGED/pull/17) --- kobo/device.go | 15 +++++++++++++++ kobo/util.go | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/kobo/device.go b/kobo/device.go index 1254b6a..6c4480d 100644 --- a/kobo/device.go +++ b/kobo/device.go @@ -310,6 +310,10 @@ func (d Device) CoverSize(t CoverType) image.Point { } } +func (d Device) CoverSized(t CoverType, orig image.Point) image.Point { + return t.Resize(d.CoverSize(t), orig) +} + func (c CoverType) NickelString() string { return string(c) } @@ -318,6 +322,17 @@ func (c CoverType) String() string { return c.NickelString() } +// Resize returnes the dimensions to resize sz to for the cover type and target size. +func (c CoverType) Resize(target image.Point, sz image.Point) image.Point { + switch c { + case CoverTypeLibList: + return resizeKeepAspectRatio(sz, target, false) + case CoverTypeLibFull, CoverTypeLibGrid: + return resizeKeepAspectRatio(sz, target, true) + } + panic("unknown cover type") +} + func (d Device) StorageGB() int { switch d { case DeviceTouchAB, DeviceTouchC, DeviceMini: diff --git a/kobo/util.go b/kobo/util.go index 0712e7d..8e6b71f 100644 --- a/kobo/util.go +++ b/kobo/util.go @@ -1,10 +1,35 @@ package kobo import ( + "image" "strconv" "strings" ) +// resizeKeepAspectRatio resizes sz to fill bounds while keeping the aspect +// ratio. It is based on the code for QSize::scaled with the modes +// Qt::KeepAspectRatio and Qt::KeepAspectRatioByExpanding. +func resizeKeepAspectRatio(sz image.Point, bounds image.Point, expand bool) image.Point { + if sz.X == 0 || sz.Y == 0 { + return sz + } + + var useHeight bool + ar := float64(sz.X) / float64(sz.Y) + rw := int(float64(bounds.Y) * ar) + + if !expand { + useHeight = rw <= bounds.X + } else { + useHeight = rw >= bounds.X + } + + if useHeight { + return image.Pt(rw, bounds.Y) + } + return image.Pt(bounds.X, int(float64(bounds.X)/ar)) +} + func strSplitInt(str string) []int64 { spl := strings.Split(str, ".") ints := make([]int64, len(spl)) From a6fcfd3d486d44f2d79cc5c72f3bb1f833152a1a Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Fri, 18 Oct 2019 22:30:14 -0400 Subject: [PATCH 04/14] Added cover path utilities * Image part hashing based on @shermp's Kobo-UNCaGED code. * Qt resizing algorithm re-implementation based on our (@NiLuJe, @geek1011) implementation for KU. * Path/ImageID/ContentID conversion based on my seriesmeta code from kepubify. --- kobo/device.go | 9 ++++++++- kobo/util.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/kobo/device.go b/kobo/device.go index 6c4480d..2252d0a 100644 --- a/kobo/device.go +++ b/kobo/device.go @@ -322,7 +322,7 @@ func (c CoverType) String() string { return c.NickelString() } -// Resize returnes the dimensions to resize sz to for the cover type and target size. +// Resize returns the dimensions to resize sz to for the cover type and target size. func (c CoverType) Resize(target image.Point, sz image.Point) image.Point { switch c { case CoverTypeLibList: @@ -333,6 +333,13 @@ func (c CoverType) Resize(target image.Point, sz image.Point) image.Point { panic("unknown cover type") } +// GeneratePath generates the path for the cover of an ImageID. The path is always +// separated with forward slashes. +func (c CoverType) GeneratePath(iid string) string { + dir1, dir2, base := hashedImageParts(iid) + return fmt.Sprintf(".kobo-images/%s/%s/%s - %s.jpg", dir1, dir2, base, c.NickelString()) +} + func (d Device) StorageGB() int { switch d { case DeviceTouchAB, DeviceTouchC, DeviceMini: diff --git a/kobo/util.go b/kobo/util.go index 8e6b71f..39e6dbd 100644 --- a/kobo/util.go +++ b/kobo/util.go @@ -1,11 +1,29 @@ package kobo import ( + "fmt" "image" + "path/filepath" "strconv" "strings" ) +// PathToContentID generates the Kobo ContentId for a path relative to the +// internal storage root (slashes are converted to forward slashes automatically). +func PathToContentID(relpath string) string { + return fmt.Sprintf("file:///mnt/onboard/%s", filepath.ToSlash(relpath)) +} + +// ContentIDToImageID converts the Kobo ContentId to the ImageId. +func ContentIDToImageID(contentID string) string { + return strings.NewReplacer( + " ", "_", + "/", "_", + ":", "_", + ".", "_", + ).Replace(contentID) +} + // resizeKeepAspectRatio resizes sz to fill bounds while keeping the aspect // ratio. It is based on the code for QSize::scaled with the modes // Qt::KeepAspectRatio and Qt::KeepAspectRatioByExpanding. @@ -30,6 +48,20 @@ func resizeKeepAspectRatio(sz image.Point, bounds image.Point, expand bool) imag return image.Pt(bounds.X, int(float64(bounds.X)/ar)) } +// hashedImageParts returns the parts needed for constructing the path to the +// cached image. The result can be applied like: +// .kobo-images/{dir1}/{dir2}/{basename} - N3_SOMETHING.jpg +func hashedImageParts(imageID string) (dir1, dir2, basename string) { + imgID := []byte(imageID) + h := uint32(0x00000000) + for _, x := range imgID { + h = (h << 4) + uint32(x) + h ^= (h & 0xf0000000) >> 23 + h &= 0x0fffffff + } + return fmt.Sprintf("%d", h&(0xff*1)), fmt.Sprintf("%d", (h&(0xff00*1))>>8), imageID +} + func strSplitInt(str string) []int64 { spl := strings.Split(str, ".") ints := make([]int64, len(spl)) From 2a705395a6c9ee2cf13d5fdd2f9540242214b940 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Fri, 18 Oct 2019 22:42:26 -0400 Subject: [PATCH 05/14] Added support for generating cover path for external dir --- kobo/device.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kobo/device.go b/kobo/device.go index 2252d0a..99bcb84 100644 --- a/kobo/device.go +++ b/kobo/device.go @@ -335,9 +335,13 @@ func (c CoverType) Resize(target image.Point, sz image.Point) image.Point { // GeneratePath generates the path for the cover of an ImageID. The path is always // separated with forward slashes. -func (c CoverType) GeneratePath(iid string) string { +func (c CoverType) GeneratePath(external bool, iid string) string { + cdir := ".kobo-images" + if external { + cdir = "koboExtStorage/images-cache" + } dir1, dir2, base := hashedImageParts(iid) - return fmt.Sprintf(".kobo-images/%s/%s/%s - %s.jpg", dir1, dir2, base, c.NickelString()) + return fmt.Sprintf("%s/%s/%s/%s - %s.jpg", cdir, dir1, dir2, base, c.NickelString()) } func (d Device) StorageGB() int { From 6166e2f6b45dbe0b1683fa49a5f73a5e2aade9ce Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Fri, 18 Oct 2019 23:47:03 -0400 Subject: [PATCH 06/14] Fixed thumbnail extension --- kobo/device.go | 2 +- kobo/util.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kobo/device.go b/kobo/device.go index 99bcb84..ecbf2ae 100644 --- a/kobo/device.go +++ b/kobo/device.go @@ -341,7 +341,7 @@ func (c CoverType) GeneratePath(external bool, iid string) string { cdir = "koboExtStorage/images-cache" } dir1, dir2, base := hashedImageParts(iid) - return fmt.Sprintf("%s/%s/%s/%s - %s.jpg", cdir, dir1, dir2, base, c.NickelString()) + return fmt.Sprintf("%s/%s/%s/%s - %s.parsed", cdir, dir1, dir2, base, c.NickelString()) } func (d Device) StorageGB() int { diff --git a/kobo/util.go b/kobo/util.go index 39e6dbd..850bb74 100644 --- a/kobo/util.go +++ b/kobo/util.go @@ -50,7 +50,7 @@ func resizeKeepAspectRatio(sz image.Point, bounds image.Point, expand bool) imag // hashedImageParts returns the parts needed for constructing the path to the // cached image. The result can be applied like: -// .kobo-images/{dir1}/{dir2}/{basename} - N3_SOMETHING.jpg +// .kobo-images/{dir1}/{dir2}/{basename} - N3_SOMETHING.parsed func hashedImageParts(imageID string) (dir1, dir2, basename string) { imgID := []byte(imageID) h := uint32(0x00000000) From e2eedacc52bb4af5f4f1b5cecdeb64584be41f71 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Sat, 19 Oct 2019 14:22:39 -0400 Subject: [PATCH 07/14] Fix CoverTypeLibFull Turns out it's actually what I originally put: N3_LIBRARY_FULL --- kobo/device.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kobo/device.go b/kobo/device.go index ecbf2ae..22ebd98 100644 --- a/kobo/device.go +++ b/kobo/device.go @@ -74,7 +74,7 @@ const ( // Cover types. const ( - CoverTypeLibFull CoverType = "N3_FULL" + CoverTypeLibFull CoverType = "N3_LIBRARY_FULL" CoverTypeLibList CoverType = "N3_LIBRARY_LIST" CoverTypeLibGrid CoverType = "N3_LIBRARY_GRID" ) From 1d859d97014987ce22f095e04c0a742f621d9ad3 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Sat, 19 Oct 2019 15:31:20 -0400 Subject: [PATCH 08/14] Update cover sizes and scaling --- kobo/device.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/kobo/device.go b/kobo/device.go index 22ebd98..b8a8d1e 100644 --- a/kobo/device.go +++ b/kobo/device.go @@ -74,6 +74,7 @@ const ( // Cover types. const ( + CoverTypeFull CoverType = "N3_FULL" CoverTypeLibFull CoverType = "N3_LIBRARY_FULL" CoverTypeLibList CoverType = "N3_LIBRARY_LIST" CoverTypeLibGrid CoverType = "N3_LIBRARY_GRID" @@ -84,7 +85,7 @@ func Devices() []Device { } func CoverTypes() []CoverType { - return []CoverType{CoverTypeLibFull, CoverTypeLibList, CoverTypeLibGrid} + return []CoverType{CoverTypeFull, CoverTypeLibFull, CoverTypeLibList, CoverTypeLibGrid} } func DeviceByID(id string) (Device, bool) { @@ -284,7 +285,9 @@ func (d Device) CoverSize(t CoverType) image.Point { return image.Pt(60, 90) } else if t == CoverTypeLibGrid { return image.Pt(149, 223) - } else if t != CoverTypeLibFull { + } else if t == CoverTypeLibFull { + return image.Pt(355, 530) + } else if t != CoverTypeFull { panic("unknown cover type") } @@ -325,9 +328,9 @@ func (c CoverType) String() string { // Resize returns the dimensions to resize sz to for the cover type and target size. func (c CoverType) Resize(target image.Point, sz image.Point) image.Point { switch c { - case CoverTypeLibList: + case CoverTypeFull: return resizeKeepAspectRatio(sz, target, false) - case CoverTypeLibFull, CoverTypeLibGrid: + case CoverTypeLibFull, CoverTypeLibGrid, CoverTypeLibList: return resizeKeepAspectRatio(sz, target, true) } panic("unknown cover type") From cdd109b95960e43dbc2fb54a4ab04eada01dc15e Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Mon, 25 Nov 2019 12:16:35 -0500 Subject: [PATCH 09/14] Finished tests for device, util, and version --- kobo/device_test.go | 86 +++++++++++++++++++++++++++++++++- kobo/util_test.go | 107 +++++++++++++++++++++++++++++++++++++++++++ kobo/version_test.go | 31 +++++++++++++ 3 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 kobo/util_test.go diff --git a/kobo/device_test.go b/kobo/device_test.go index 1d9a20b..9ece5fb 100644 --- a/kobo/device_test.go +++ b/kobo/device_test.go @@ -2,10 +2,13 @@ package kobo import ( "fmt" + "image" + "reflect" "testing" ) func TestDeviceList(t *testing.T) { + // check this manually (automatically doing this would just be a duplicate of tbe info) for _, d := range Devices() { fmt.Printf("Device %d (%s):\n Family: %s (%s)\n Hardware: %s\n IDString: %s\n Storage: %dGB\n CodeNames: %s\n Cover Types:\n", int(d), d.Name(), d.Family(), d.CodeNames().Family(), d.Hardware(), d.IDString(), d.StorageGB(), d.CodeNames()) for _, c := range CoverTypes() { @@ -18,4 +21,85 @@ func TestDeviceList(t *testing.T) { } } -// TODO: more tests +func TestCoverGeneratePath(t *testing.T) { + for _, tc := range []struct { + ct CoverType + ext bool + iid string + out string + }{ + // note: the image ids and content ids are already tested, no need to do that here; just test the hashing and file path + { + CoverTypeFull, false, + "file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_1_kepub_epub", + ".kobo-images/210/143/file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_1_kepub_epub - N3_FULL.parsed", + }, + { + CoverTypeLibFull, false, + "file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_1_kepub_epub", + ".kobo-images/210/143/file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_1_kepub_epub - N3_LIBRARY_FULL.parsed", + }, + { + CoverTypeLibGrid, false, + "file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_1_kepub_epub", + ".kobo-images/210/143/file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_1_kepub_epub - N3_LIBRARY_GRID.parsed", + }, + { + CoverTypeLibList, false, + "file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_1_kepub_epub", + ".kobo-images/210/143/file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_1_kepub_epub - N3_LIBRARY_LIST.parsed", + }, + { + CoverTypeLibList, true, + "file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_2__kepub_epub", + "koboExtStorage/images-cache/82/246/file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_2__kepub_epub - N3_LIBRARY_LIST.parsed", + }, + } { + if path := tc.ct.GeneratePath(tc.ext, tc.iid); path != tc.out { + t.Errorf("(%s, ext: %t, iid: %#v): expected %#v, got %#v", tc.ct, tc.ext, tc.iid, tc.out, path) + } + } +} + +func TestSwitchCases(t *testing.T) { + for _, d := range Devices() { + for _, fn := range []interface{}{ + d.CodeNames, + d.Family, + d.Hardware, + d.ID, + d.IDString, + d.Name, + d.StorageGB, + d.String, + } { + if panics(fn) { + t.Errorf("%s: %s panics", d, reflect.ValueOf(fn)) + } + } + for _, ct := range CoverTypes() { + if panics(func() image.Point { return d.CoverSize(ct) }) { + t.Errorf("%s: CoverSize panics for %s", d, ct) + } + } + } +} + +func panics(fn interface{}) (panicked bool) { + v := reflect.ValueOf(fn) + if t := v.Type(); t.Kind() != reflect.Func { + panic("not a func") + } else if t.NumIn() != 0 { + panic("func requires args") + } + + defer func() { + if err := recover(); err != nil { + fmt.Println(err) + panicked = true + } + }() + v.Call(nil) + + return false +} diff --git a/kobo/util_test.go b/kobo/util_test.go new file mode 100644 index 0000000..10ac802 --- /dev/null +++ b/kobo/util_test.go @@ -0,0 +1,107 @@ +package kobo + +import ( + "image" + "path/filepath" + "testing" +) + +func TestPathContentIDImageID(t *testing.T) { + for _, tc := range []struct { + path string + cid string + iid string + }{ + { + "kepubify/Books_converted/Patrick Gaskin/Test Book 1.kepub.epub", + "file:///mnt/onboard/kepubify/Books_converted/Patrick Gaskin/Test Book 1.kepub.epub", + "file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_1_kepub_epub", + }, + { + "kepubify/Books_converted/Patrick Gaskin/Test Book 2:.kepub.epub", + "file:///mnt/onboard/kepubify/Books_converted/Patrick Gaskin/Test Book 2:.kepub.epub", + "file____mnt_onboard_kepubify_Books_converted_Patrick_Gaskin_Test_Book_2__kepub_epub", + }, + } { + cid := PathToContentID(tc.path) + iid := ContentIDToImageID(tc.cid) + + if cid != PathToContentID(filepath.FromSlash(tc.path)) { + t.Errorf("incorrect native path separator conversion") + } + + if tc.cid != cid { + t.Errorf("cid of %#v: expected %#v, got %#v", tc.path, tc.cid, cid) + } + + if tc.iid != iid { + t.Errorf("iid of %#v: expected %#v, got %#v", tc.cid, tc.iid, iid) + } + } +} + +func TestResizeKeepAspectRatioExpand(t *testing.T) { + for _, tc := range []struct { + sz image.Point + bounds image.Point + rsz image.Point + }{ + // don't resize if width or height is zero + {image.Pt(0, 0), image.Pt(0, 0), image.Pt(0, 0)}, + {image.Pt(1, 0), image.Pt(0, 0), image.Pt(1, 0)}, + {image.Pt(0, 1), image.Pt(0, 0), image.Pt(0, 1)}, + // same aspect ratio + {image.Pt(1, 1), image.Pt(1, 1), image.Pt(1, 1)}, + {image.Pt(1, 1), image.Pt(5, 5), image.Pt(5, 5)}, + {image.Pt(5, 5), image.Pt(1, 1), image.Pt(1, 1)}, + // limited by width + {image.Pt(2, 3), image.Pt(6, 6), image.Pt(6, 9)}, + {image.Pt(2, 4), image.Pt(6, 6), image.Pt(6, 12)}, + {image.Pt(6, 9), image.Pt(2, 3), image.Pt(2, 3)}, + {image.Pt(6, 12), image.Pt(2, 4), image.Pt(2, 4)}, + // limited by height + {image.Pt(3, 2), image.Pt(6, 6), image.Pt(9, 6)}, + {image.Pt(4, 2), image.Pt(6, 6), image.Pt(12, 6)}, + {image.Pt(9, 6), image.Pt(3, 2), image.Pt(3, 2)}, + {image.Pt(12, 6), image.Pt(4, 2), image.Pt(4, 2)}, + // fractional stuff + {image.Pt(1391, 2200), image.Pt(355, 530), image.Pt(355, 561)}, + } { + if tsz := resizeKeepAspectRatio(tc.sz, tc.bounds, true); !tsz.Eq(tc.rsz) { + t.Errorf("(%s, %s, %t): expected %s, got %s", tc.sz, tc.bounds, true, tc.rsz, tsz) + } + } +} + +func TestResizeKeepAspectRatioShrink(t *testing.T) { + for _, tc := range []struct { + sz image.Point + bounds image.Point + rsz image.Point + }{ + // don't resize if width or height is zero + {image.Pt(0, 0), image.Pt(0, 0), image.Pt(0, 0)}, + {image.Pt(1, 0), image.Pt(0, 0), image.Pt(1, 0)}, + {image.Pt(0, 1), image.Pt(0, 0), image.Pt(0, 1)}, + // same aspect ratio + {image.Pt(1, 1), image.Pt(1, 1), image.Pt(1, 1)}, + {image.Pt(1, 1), image.Pt(5, 5), image.Pt(5, 5)}, + {image.Pt(5, 5), image.Pt(1, 1), image.Pt(1, 1)}, + // limited by width + {image.Pt(2, 3), image.Pt(6, 6), image.Pt(4, 6)}, + {image.Pt(2, 4), image.Pt(6, 6), image.Pt(3, 6)}, + {image.Pt(6, 9), image.Pt(2, 3), image.Pt(2, 3)}, + {image.Pt(6, 12), image.Pt(2, 4), image.Pt(2, 4)}, + // limited by height + {image.Pt(3, 2), image.Pt(6, 6), image.Pt(6, 4)}, + {image.Pt(4, 2), image.Pt(6, 6), image.Pt(6, 3)}, + {image.Pt(9, 6), image.Pt(3, 2), image.Pt(3, 2)}, + {image.Pt(12, 6), image.Pt(4, 2), image.Pt(4, 2)}, + // fractional stuff + {image.Pt(1391, 2200), image.Pt(355, 530), image.Pt(335, 530)}, + } { + if tsz := resizeKeepAspectRatio(tc.sz, tc.bounds, false); !tsz.Eq(tc.rsz) { + t.Errorf("(%s, %s, %t): expected %s, got %s", tc.sz, tc.bounds, false, tc.rsz, tsz) + } + } +} diff --git a/kobo/version_test.go b/kobo/version_test.go index ae4793b..228480b 100644 --- a/kobo/version_test.go +++ b/kobo/version_test.go @@ -53,6 +53,32 @@ func TestParseKoboVersion(t *testing.T) { } } +func TestParseKoboAffiliate(t *testing.T) { + if err := fakekobo(func(kpath string) { + aff, err := ParseKoboAffiliate(kpath) + if err != nil { + t.Error(err) + } else if aff != "Kobo" { + t.Errorf("expected Kobo, got %#v", aff) + } + }); err != nil { + t.Fatal(err) + } +} + +func TestIsKobo(t *testing.T) { + if err := fakekobo(func(kpath string) { + if !IsKobo(kpath) { + t.Errorf("expected fake kobo to be a kobo") + } + }); err != nil { + t.Fatal(err) + } + if IsKobo(".") { + t.Errorf("expected current dir not to be a kobo") + } +} + func fakekobo(fn func(kpath string)) error { td, err := ioutil.TempDir("", "koboutils") if err != nil { @@ -70,6 +96,11 @@ func fakekobo(fn func(kpath string)) error { return fmt.Errorf("could not write fake version file: %v", err) } + err = ioutil.WriteFile(filepath.Join(td, ".kobo", "affiliate.conf"), []byte("[General]\naffiliate=Kobo"), 0644) + if err != nil { + return fmt.Errorf("could not write fake affiliate file: %v", err) + } + fn(td) return nil From 7aaa9200624db90197781392b8fd937b4a85b370 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Mon, 25 Nov 2019 13:08:41 -0500 Subject: [PATCH 10/14] Added documentation for device stuff --- kobo/device.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/kobo/device.go b/kobo/device.go index b8a8d1e..7d11aec 100644 --- a/kobo/device.go +++ b/kobo/device.go @@ -1,3 +1,4 @@ +// Package kobo contains stuff related to Kobo devices, firmware, and nickel. package kobo import ( @@ -8,10 +9,26 @@ import ( // See https://gist.github.com/geek1011/613b34c23f026f7c39c50ee32f5e167e and // https://github.com/shermp/Kobo-UNCaGED/issues/16 +// Device is a device model. type Device int + +// Hardware is a hardware revision. type Hardware int -type CodeName string -type CodeNameTriplet [3]CodeName + +// CodeNames are used to identify the device category, devices, and variations. +type ( + // CodeName represents an individual codename. Note that a codename can be + // used for more than one thing in a triplet. + CodeName string + + // CodeNameTriplet represents a triplet of class/family/secondary codenames. + // Note that nothing in nickel says a device can only have 3, but everything + // so far implies that (and it makes sense). + CodeNameTriplet [3]CodeName +) + +// CoverType is used to identify different cover dimensions used for different +// purposes by nickel. type CoverType string // Devices (not including really old ones, like Kobo eReader, Wireless, Literati, and Vox). @@ -80,14 +97,17 @@ const ( CoverTypeLibGrid CoverType = "N3_LIBRARY_GRID" ) +// Devices returns a slice of all supported devices. func Devices() []Device { return []Device{DeviceTouchAB, DeviceTouchC, DeviceGlo, DeviceMini, DeviceAuraHD, DeviceAura, DeviceAuraH2O, DeviceGloHD, DeviceTouch2, DeviceAuraONE, DeviceAuraH2OEdition2v1, DeviceAuraEdition2v1, DeviceClaraHD, DeviceForma, DeviceAuraH2OEdition2v2, DeviceAuraEdition2v2, DeviceForma32, DeviceAuraONELimitedEdition, DeviceLibraH2O} } +// CoverTypes returns a slice of all implemented nickel cover types. func CoverTypes() []CoverType { return []CoverType{CoverTypeFull, CoverTypeLibFull, CoverTypeLibList, CoverTypeLibGrid} } +// DeviceByID gets a device by its full ID string. func DeviceByID(id string) (Device, bool) { for _, device := range Devices() { if device.IDString() == id { @@ -97,10 +117,12 @@ func DeviceByID(id string) (Device, bool) { return 0, false } +// ID returns the numerical device ID. func (d Device) ID() int { return int(d) } +// IDString returns the full ID string. func (d Device) IDString() string { return fmt.Sprintf("00000000-0000-0000-0000-%012d", d.ID()) } @@ -109,6 +131,7 @@ func (d Device) String() string { return d.Name() } +// Name returns the full device name. func (d Device) Name() string { cd := d.CodeNames() dev := cd.FamilyString() @@ -132,6 +155,7 @@ func (d Device) Name() string { return dev } +// Hardware returns the hardware revision. func (d Device) Hardware() Hardware { switch d { case DeviceTouchAB: @@ -148,6 +172,7 @@ func (d Device) Hardware() Hardware { panic("unknown device") } +// Hardware returns the numerical hardware revision. func (h Hardware) Hardware() int { return int(h) } @@ -156,6 +181,17 @@ func (h Hardware) String() string { return fmt.Sprintf("kobo%d", int(h)) } +// Is replicates the Device::is* functions in libnickel. +func (d Device) Is(n CodeName) bool { + cn := d.CodeNames() + return n != CodeNameNone && (cn.Class() == n || cn.Family() == n || cn.Secondary() == n) +} + +// CodeNames returns the codename triplet for the device (like libnickel). Note: +// Nickel has a slightly different definition if Class, Family, and Secondary, +// but these triplets are correct (i.e. Device::is* will match nickel, and the +// hierachy is correct). These were determined by static analysis of libnickel. +// See PR#1 for details. func (d Device) CodeNames() CodeNameTriplet { switch d { case DeviceTouchAB, DeviceTouchC: @@ -208,18 +244,22 @@ func (c CodeNameTriplet) String() string { return fmt.Sprintf("class=%s family=%s", c[0], c[1]) } +// Family is short for Device.CodeNames().FamilyString(). func (d Device) Family() string { return d.CodeNames().FamilyString() } +// Class gets the class/category. func (c CodeNameTriplet) Class() CodeName { return c[0] } +// Family gets the family/model (i.e. part of a class) func (c CodeNameTriplet) Family() CodeName { return c[1] } +// FamilyString gets the human readable family/model. func (c CodeNameTriplet) FamilyString() string { switch c.Family() { case CodeNameDesktop: @@ -264,10 +304,13 @@ func (c CodeNameTriplet) FamilyString() string { panic("unknown family") } +// Secondary gets the secondary device codename (i.e. refines the family). func (c CodeNameTriplet) Secondary() CodeName { return c[2] } +// SecondaryString returns the human readable string to append to FamilyString +// if applicable (e.g. Limited Edition, 32GB). func (c CodeNameTriplet) SecondaryString() string { switch c.Secondary() { case CodeNameNone: @@ -280,6 +323,8 @@ func (c CodeNameTriplet) SecondaryString() string { panic("unknown secondary") } +// CoverSize returns the cover size for a cover type for a Device. Currently, +// everything except for the Full cover is the same for every device. func (d Device) CoverSize(t CoverType) image.Point { if t == CoverTypeLibList { return image.Pt(60, 90) @@ -313,10 +358,14 @@ func (d Device) CoverSize(t CoverType) image.Point { } } +// CoverSized returns a size resized to the correct size using the same logic as +// nickel. func (d Device) CoverSized(t CoverType, orig image.Point) image.Point { return t.Resize(d.CoverSize(t), orig) } +// NickelString returns the internal string used in nickel to identify the cover +// type. func (c CoverType) NickelString() string { return string(c) } @@ -347,6 +396,7 @@ func (c CoverType) GeneratePath(external bool, iid string) string { return fmt.Sprintf("%s/%s/%s/%s - %s.parsed", cdir, dir1, dir2, base, c.NickelString()) } +// StorageGB returns the advertised storage capacity of a Device. func (d Device) StorageGB() int { switch d { case DeviceTouchAB, DeviceTouchC, DeviceMini: From cc62448f3072489c6ae6ce1c00e0c76078f76b03 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Mon, 25 Nov 2019 13:10:34 -0500 Subject: [PATCH 11/14] Updated Go to 1.13 for CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index befcc07..5b0acbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go go: - - "1.10" + - "1.13" script: - make clean From ac79110b1690046e04b5dcfafc305ba3928bf532 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Mon, 25 Nov 2019 13:15:19 -0500 Subject: [PATCH 12/14] Updated README --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 00907c3..62aaa4a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ # koboutils -Short utilities to do stuff with Kobo eReaders. +Utilities to do stuff with Kobo eReaders. Can be used as a library: godoc.org/github.com/geek1011/koboutils/kobo. -Also can be used as a library: godoc.org/github.com/geek1011/koboutils/kobo \ No newline at end of file +**Features:** +- Full support for device codenames (class, family, secondary). +- Device specs. +- Upgrade checks. +- Device detection. +- Cover image resizing. +- Firmware version and date extraction. From 52bf3c167c07b05359f518ab049137a35d30fddd Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Mon, 25 Nov 2019 13:20:01 -0500 Subject: [PATCH 13/14] Switch to v2 module --- README.md | 2 +- go.mod | 2 +- kobo-find/main.go | 2 +- kobo-info/main.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 62aaa4a..93cb266 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # koboutils -Utilities to do stuff with Kobo eReaders. Can be used as a library: godoc.org/github.com/geek1011/koboutils/kobo. +Utilities to do stuff with Kobo eReaders. Can be used as a library: godoc.org/github.com/geek1011/koboutils/v2/kobo. **Features:** - Full support for device codenames (class, family, secondary). diff --git a/go.mod b/go.mod index f4289fd..0e4c3c7 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/geek1011/koboutils +module github.com/geek1011/koboutils/v2 go 1.13 diff --git a/kobo-find/main.go b/kobo-find/main.go index 270ae49..2006435 100644 --- a/kobo-find/main.go +++ b/kobo-find/main.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/geek1011/koboutils/kobo" + "github.com/geek1011/koboutils/v2/kobo" "github.com/spf13/pflag" ) diff --git a/kobo-info/main.go b/kobo-info/main.go index 4c32620..c882246 100644 --- a/kobo-info/main.go +++ b/kobo-info/main.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/geek1011/koboutils/kobo" + "github.com/geek1011/koboutils/v2/kobo" "github.com/spf13/pflag" ) From dcc8686e83c38a21a1f4cbeeb37c6cbe57d3f8aa Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Mon, 25 Nov 2019 13:21:40 -0500 Subject: [PATCH 14/14] Updated Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 47f0989..7032aa4 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,8 @@ clean: rm -rfv build deps: - go get github.com/tcnksm/ghr - go get github.com/mholt/archiver/cmd/arc + GO111MODULE=on go install github.com/tcnksm/ghr + GO111MODULE=on go install github.com/mholt/archiver/cmd/arc test: go test ./...