[add] tests, [add] refactor epub feat
This commit is contained in:
parent
bf6ac96376
commit
b5d5e4bd64
7
Makefile
7
Makefile
@ -25,3 +25,10 @@ docker_build_release_latest:
|
|||||||
-t gitea.va.reichard.io/evan/bookmanager:latest \
|
-t gitea.va.reichard.io/evan/bookmanager:latest \
|
||||||
-t gitea.va.reichard.io/evan/bookmanager:`git describe --tags` \
|
-t gitea.va.reichard.io/evan/bookmanager:`git describe --tags` \
|
||||||
--push .
|
--push .
|
||||||
|
|
||||||
|
tests_integration:
|
||||||
|
go test -v -tags=integration -coverpkg=./... ./metadata
|
||||||
|
|
||||||
|
|
||||||
|
tests_unit:
|
||||||
|
SET_TEST=set_val go test -v -coverpkg=./... ./...
|
||||||
|
@ -13,8 +13,6 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"reichard.io/bbank/config"
|
"reichard.io/bbank/config"
|
||||||
"reichard.io/bbank/database"
|
"reichard.io/bbank/database"
|
||||||
"reichard.io/bbank/graph"
|
|
||||||
"reichard.io/bbank/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type API struct {
|
type API struct {
|
||||||
@ -75,9 +73,9 @@ func (api *API) registerWebAppRoutes() {
|
|||||||
// Define Templates & Helper Functions
|
// Define Templates & Helper Functions
|
||||||
render := multitemplate.NewRenderer()
|
render := multitemplate.NewRenderer()
|
||||||
helperFuncs := template.FuncMap{
|
helperFuncs := template.FuncMap{
|
||||||
"GetSVGGraphData": graph.GetSVGGraphData,
|
"GetSVGGraphData": getSVGGraphData,
|
||||||
"GetUTCOffsets": utils.GetUTCOffsets,
|
"GetUTCOffsets": getUTCOffsets,
|
||||||
"NiceSeconds": utils.NiceSeconds,
|
"NiceSeconds": niceSeconds,
|
||||||
}
|
}
|
||||||
|
|
||||||
render.AddFromFilesFuncs("login", helperFuncs, "templates/login.html")
|
render.AddFromFilesFuncs("login", helperFuncs, "templates/login.html")
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
"reichard.io/bbank/database"
|
"reichard.io/bbank/database"
|
||||||
"reichard.io/bbank/metadata"
|
"reichard.io/bbank/metadata"
|
||||||
"reichard.io/bbank/search"
|
"reichard.io/bbank/search"
|
||||||
"reichard.io/bbank/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type queryParams struct {
|
type queryParams struct {
|
||||||
@ -587,7 +586,7 @@ func (api *API) saveNewDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate Partial MD5 ID
|
// Calculate Partial MD5 ID
|
||||||
partialMD5, err := utils.CalculatePartialMD5(tempFilePath)
|
partialMD5, err := calculatePartialMD5(tempFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("[saveNewDocument] Partial MD5 Error: ", err)
|
log.Warn("[saveNewDocument] Partial MD5 Error: ", err)
|
||||||
c.AbortWithStatus(http.StatusBadRequest)
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package utils
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -7,6 +7,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"reichard.io/bbank/database"
|
||||||
|
"reichard.io/bbank/graph"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UTCOffset struct {
|
type UTCOffset struct {
|
||||||
@ -55,11 +58,11 @@ var UTC_OFFSETS = []UTCOffset{
|
|||||||
{Value: "+14 hours", Name: "UTC+14:00"},
|
{Value: "+14 hours", Name: "UTC+14:00"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUTCOffsets() []UTCOffset {
|
func getUTCOffsets() []UTCOffset {
|
||||||
return UTC_OFFSETS
|
return UTC_OFFSETS
|
||||||
}
|
}
|
||||||
|
|
||||||
func NiceSeconds(input int64) (result string) {
|
func niceSeconds(input int64) (result string) {
|
||||||
if input == 0 {
|
if input == 0 {
|
||||||
return "N/A"
|
return "N/A"
|
||||||
}
|
}
|
||||||
@ -88,7 +91,7 @@ func NiceSeconds(input int64) (result string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reimplemented KOReader Partial MD5 Calculation
|
// Reimplemented KOReader Partial MD5 Calculation
|
||||||
func CalculatePartialMD5(filePath string) (string, error) {
|
func calculatePartialMD5(filePath string) (string, error) {
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -121,3 +124,13 @@ func CalculatePartialMD5(filePath string) (string, error) {
|
|||||||
allBytes := buf.Bytes()
|
allBytes := buf.Bytes()
|
||||||
return fmt.Sprintf("%x", md5.Sum(allBytes)), nil
|
return fmt.Sprintf("%x", md5.Sum(allBytes)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert Database Array -> Int64 Array
|
||||||
|
func getSVGGraphData(inputData []database.GetDailyReadStatsRow, svgWidth int, svgHeight int) graph.SVGGraphData {
|
||||||
|
var intData []int64
|
||||||
|
for _, item := range inputData {
|
||||||
|
intData = append(intData, item.MinutesRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph.GetSVGGraphData(intData, svgWidth, svgHeight)
|
||||||
|
}
|
@ -13,7 +13,6 @@ type Config struct {
|
|||||||
// DB Configuration
|
// DB Configuration
|
||||||
DBType string
|
DBType string
|
||||||
DBName string
|
DBName string
|
||||||
DBPassword string
|
|
||||||
|
|
||||||
// Data Paths
|
// Data Paths
|
||||||
ConfigPath string
|
ConfigPath string
|
||||||
@ -30,7 +29,6 @@ func Load() *Config {
|
|||||||
Version: "0.0.2",
|
Version: "0.0.2",
|
||||||
DBType: trimLowerString(getEnv("DATABASE_TYPE", "SQLite")),
|
DBType: trimLowerString(getEnv("DATABASE_TYPE", "SQLite")),
|
||||||
DBName: trimLowerString(getEnv("DATABASE_NAME", "book_manager")),
|
DBName: trimLowerString(getEnv("DATABASE_NAME", "book_manager")),
|
||||||
DBPassword: getEnv("DATABASE_PASSWORD", ""),
|
|
||||||
ConfigPath: getEnv("CONFIG_PATH", "/config"),
|
ConfigPath: getEnv("CONFIG_PATH", "/config"),
|
||||||
DataPath: getEnv("DATA_PATH", "/data"),
|
DataPath: getEnv("DATA_PATH", "/data"),
|
||||||
ListenPort: getEnv("LISTEN_PORT", "8585"),
|
ListenPort: getEnv("LISTEN_PORT", "8585"),
|
||||||
|
35
config/config_test.go
Normal file
35
config/config_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestLoadConfig(t *testing.T) {
|
||||||
|
conf := Load()
|
||||||
|
want := "sqlite"
|
||||||
|
if conf.DBType != want {
|
||||||
|
t.Fatalf(`Load().DBType = %q, want match for %#q, nil`, conf.DBType, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetEnvDefault(t *testing.T) {
|
||||||
|
want := "def_val"
|
||||||
|
envDefault := getEnv("DEFAULT_TEST", want)
|
||||||
|
if envDefault != want {
|
||||||
|
t.Fatalf(`getEnv("DEFAULT_TEST", "def_val") = %q, want match for %#q, nil`, envDefault, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetEnvSet(t *testing.T) {
|
||||||
|
envDefault := getEnv("SET_TEST", "not_this")
|
||||||
|
want := "set_val"
|
||||||
|
if envDefault != want {
|
||||||
|
t.Fatalf(`getEnv("SET_TEST", "not_this") = %q, want match for %#q, nil`, envDefault, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimLowerString(t *testing.T) {
|
||||||
|
want := "trimtest"
|
||||||
|
output := trimLowerString(" trimTest ")
|
||||||
|
if output != want {
|
||||||
|
t.Fatalf(`trimLowerString(" trimTest ") = %q, want match for %#q, nil`, output, want)
|
||||||
|
}
|
||||||
|
}
|
@ -6,14 +6,9 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
"path"
|
"path"
|
||||||
"reichard.io/bbank/config"
|
"reichard.io/bbank/config"
|
||||||
|
|
||||||
// CGO SQLite
|
|
||||||
// sqlite "github.com/mattn/go-sqlite3"
|
|
||||||
|
|
||||||
// GO SQLite
|
|
||||||
_ "modernc.org/sqlite"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DBManager struct {
|
type DBManager struct {
|
||||||
@ -35,9 +30,12 @@ func NewMgr(c *config.Config) *DBManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create Database
|
// Create Database
|
||||||
|
if c.DBType == "sqlite" || c.DBType == "memory" {
|
||||||
|
var dbLocation string = ":memory:"
|
||||||
if c.DBType == "sqlite" {
|
if c.DBType == "sqlite" {
|
||||||
// GO SQLite
|
dbLocation = path.Join(c.ConfigPath, fmt.Sprintf("%s.db", c.DBName))
|
||||||
dbLocation := path.Join(c.ConfigPath, fmt.Sprintf("%s.db", c.DBName))
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
dbm.DB, err = sql.Open("sqlite", dbLocation)
|
dbm.DB, err = sql.Open("sqlite", dbLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -49,19 +47,6 @@ func NewMgr(c *config.Config) *DBManager {
|
|||||||
if _, err := dbm.DB.Exec(ddl, nil); err != nil {
|
if _, err := dbm.DB.Exec(ddl, nil); err != nil {
|
||||||
log.Info("Exec Error:", err)
|
log.Info("Exec Error:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CGO SQLite
|
|
||||||
// sql.Register("sqlite3_custom", &sqlite.SQLiteDriver{
|
|
||||||
// ConnectHook: connectHookSQLite,
|
|
||||||
// })
|
|
||||||
|
|
||||||
// dbLocation := path.Join(c.ConfigPath, fmt.Sprintf("%s.db", c.DBName))
|
|
||||||
|
|
||||||
// var err error
|
|
||||||
// dbm.DB, err = sql.Open("sqlite3_custom", dbLocation)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Fatal(err)
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
log.Fatal("Unsupported Database")
|
log.Fatal("Unsupported Database")
|
||||||
}
|
}
|
||||||
@ -77,15 +62,3 @@ func (dbm *DBManager) CacheTempTables() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// CGO SQLite
|
|
||||||
func connectHookSQLite(conn *sqlite.SQLiteConn) error {
|
|
||||||
// Create Tables
|
|
||||||
log.Debug("Creating Schema")
|
|
||||||
if _, err := conn.Exec(ddl, nil); err != nil {
|
|
||||||
log.Warn("Create Schema Failure: ", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
213
database/manager_test.go
Normal file
213
database/manager_test.go
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"reichard.io/bbank/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type databaseTest struct {
|
||||||
|
*testing.T
|
||||||
|
dbm *DBManager
|
||||||
|
}
|
||||||
|
|
||||||
|
var userID string = "testUser"
|
||||||
|
var userPass string = "testPass"
|
||||||
|
var deviceID string = "testDevice"
|
||||||
|
var deviceName string = "testDeviceName"
|
||||||
|
var documentID string = "testDocument"
|
||||||
|
var documentTitle string = "testTitle"
|
||||||
|
var documentAuthor string = "testAuthor"
|
||||||
|
|
||||||
|
func TestNewMgr(t *testing.T) {
|
||||||
|
cfg := config.Config{
|
||||||
|
DBType: "memory",
|
||||||
|
}
|
||||||
|
|
||||||
|
dbm := NewMgr(&cfg)
|
||||||
|
if dbm == nil {
|
||||||
|
t.Fatalf(`Expected: *DBManager, Got: nil`)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Database", func(t *testing.T) {
|
||||||
|
dt := databaseTest{t, dbm}
|
||||||
|
dt.TestUser()
|
||||||
|
dt.TestDocument()
|
||||||
|
dt.TestDevice()
|
||||||
|
dt.TestActivity()
|
||||||
|
dt.TestDailyReadStats()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *databaseTest) TestUser() {
|
||||||
|
dt.Run("User", func(t *testing.T) {
|
||||||
|
changed, err := dt.dbm.Queries.CreateUser(dt.dbm.Ctx, CreateUserParams{
|
||||||
|
ID: userID,
|
||||||
|
Pass: &userPass,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil || changed != 1 {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v, Error: %v`, 1, changed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := dt.dbm.Queries.GetUser(dt.dbm.Ctx, userID)
|
||||||
|
if err != nil || *user.Pass != userPass {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v, Error: %v`, userPass, *user.Pass, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *databaseTest) TestDocument() {
|
||||||
|
dt.Run("Document", func(t *testing.T) {
|
||||||
|
doc, err := dt.dbm.Queries.UpsertDocument(dt.dbm.Ctx, UpsertDocumentParams{
|
||||||
|
ID: documentID,
|
||||||
|
Title: &documentTitle,
|
||||||
|
Author: &documentAuthor,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Expected: Document, Got: %v, Error: %v`, doc, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if doc.ID != documentID {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, documentID, doc.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *doc.Title != documentTitle {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, documentTitle, *doc.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *doc.Author != documentAuthor {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, documentAuthor, *doc.Author)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *databaseTest) TestDevice() {
|
||||||
|
dt.Run("Device", func(t *testing.T) {
|
||||||
|
device, err := dt.dbm.Queries.UpsertDevice(dt.dbm.Ctx, UpsertDeviceParams{
|
||||||
|
ID: deviceID,
|
||||||
|
UserID: userID,
|
||||||
|
DeviceName: deviceName,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Expected: Device, Got: %v, Error: %v`, device, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.ID != deviceID {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, deviceID, device.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.UserID != userID {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, userID, device.UserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.DeviceName != deviceName {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, deviceName, device.DeviceName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *databaseTest) TestActivity() {
|
||||||
|
dt.Run("Progress", func(t *testing.T) {
|
||||||
|
// 10 Activities, 10 Days
|
||||||
|
end := time.Now()
|
||||||
|
start := end.AddDate(0, 0, -9)
|
||||||
|
var counter int64 = 0
|
||||||
|
|
||||||
|
for d := start; d.After(end) == false; d = d.AddDate(0, 0, 1) {
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
// Add Item
|
||||||
|
activity, err := dt.dbm.Queries.AddActivity(dt.dbm.Ctx, AddActivityParams{
|
||||||
|
DocumentID: documentID,
|
||||||
|
DeviceID: deviceID,
|
||||||
|
UserID: userID,
|
||||||
|
StartTime: d.UTC().Format(time.RFC3339),
|
||||||
|
Duration: 60,
|
||||||
|
Page: counter,
|
||||||
|
Pages: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Validate No Error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`expected: rawactivity, got: %v, error: %v`, activity, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Auto Increment Working
|
||||||
|
if activity.ID != counter {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, counter, activity.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initiate Cache
|
||||||
|
if err := dt.dbm.CacheTempTables(); err != nil {
|
||||||
|
t.Fatalf(`Error: %v`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Exists
|
||||||
|
existsRows, err := dt.dbm.Queries.GetActivity(dt.dbm.Ctx, GetActivityParams{
|
||||||
|
UserID: userID,
|
||||||
|
Offset: 0,
|
||||||
|
Limit: 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Expected: []GetActivityRow, Got: %v, Error: %v`, existsRows, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(existsRows) != 10 {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, 10, len(existsRows))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Doesn't Exist
|
||||||
|
doesntExistsRows, err := dt.dbm.Queries.GetActivity(dt.dbm.Ctx, GetActivityParams{
|
||||||
|
UserID: userID,
|
||||||
|
DocumentID: "unknownDoc",
|
||||||
|
DocFilter: true,
|
||||||
|
Offset: 0,
|
||||||
|
Limit: 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Expected: []GetActivityRow, Got: %v, Error: %v`, doesntExistsRows, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(doesntExistsRows) != 0 {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, 0, len(doesntExistsRows))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *databaseTest) TestDailyReadStats() {
|
||||||
|
dt.Run("DailyReadStats", func(t *testing.T) {
|
||||||
|
readStats, err := dt.dbm.Queries.GetDailyReadStats(dt.dbm.Ctx, userID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Expected: []GetDailyReadStatsRow, Got: %v, Error: %v`, readStats, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate 30 Days Stats
|
||||||
|
if len(readStats) != 30 {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, 30, len(readStats))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate 1 Minute / Day - Last 10 Days
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
stat := readStats[i]
|
||||||
|
if stat.MinutesRead != 1 {
|
||||||
|
t.Fatalf(`Day: %v, Expected: %v, Got: %v`, stat.Date, 1, stat.MinutesRead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate 0 Minute / Day - Remaining 20 Days
|
||||||
|
for i := 10; i < 30; i++ {
|
||||||
|
stat := readStats[i]
|
||||||
|
if stat.MinutesRead != 0 {
|
||||||
|
t.Fatalf(`Day: %v, Expected: %v, Got: %v`, stat.Date, 0, stat.MinutesRead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
3
go.mod
3
go.mod
@ -3,6 +3,7 @@ module reichard.io/bbank
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/PuerkitoBio/goquery v1.8.1
|
||||||
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736
|
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2
|
github.com/gabriel-vasile/mimetype v1.4.2
|
||||||
github.com/gin-contrib/multitemplate v0.0.0-20230212012517-45920c92c271
|
github.com/gin-contrib/multitemplate v0.0.0-20230212012517-45920c92c271
|
||||||
@ -10,6 +11,7 @@ require (
|
|||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.25
|
github.com/microcosm-cc/bluemonday v1.0.25
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
github.com/taylorskalyo/goreader v0.0.0-20230626212555-e7f5644f8115
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
github.com/urfave/cli/v2 v2.25.7
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||||
golang.org/x/net v0.15.0
|
golang.org/x/net v0.15.0
|
||||||
@ -17,7 +19,6 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.8.1 // indirect
|
|
||||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/bytedance/sonic v1.10.0 // indirect
|
github.com/bytedance/sonic v1.10.0 // indirect
|
||||||
|
3
go.sum
3
go.sum
@ -146,6 +146,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
|||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/taylorskalyo/goreader v0.0.0-20230626212555-e7f5644f8115 h1:OEAIMYp5l9kJ2kT9UPL5QSUriKIIDhnLmpJTy69sltA=
|
||||||
|
github.com/taylorskalyo/goreader v0.0.0-20230626212555-e7f5644f8115/go.mod h1:AIVbkIe1G7fpFHiKOdxZnU5p9tFPYNTQyH3H5IrRkGw=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
@ -225,7 +227,6 @@ golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
|||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
|
@ -3,8 +3,6 @@ package graph
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"reichard.io/bbank/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SVGGraphPoint struct {
|
type SVGGraphPoint struct {
|
||||||
@ -28,12 +26,12 @@ type SVGBezierOpposedLine struct {
|
|||||||
Angle int
|
Angle int
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSVGGraphData(inputData []database.GetDailyReadStatsRow, svgWidth int, svgHeight int) SVGGraphData {
|
func GetSVGGraphData(inputData []int64, svgWidth int, svgHeight int) SVGGraphData {
|
||||||
// Derive Height
|
// Derive Height
|
||||||
var maxHeight int = 0
|
var maxHeight int = 0
|
||||||
for _, item := range inputData {
|
for _, item := range inputData {
|
||||||
if int(item.MinutesRead) > maxHeight {
|
if int(item) > maxHeight {
|
||||||
maxHeight = int(item.MinutesRead)
|
maxHeight = int(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +53,7 @@ func GetSVGGraphData(inputData []database.GetDailyReadStatsRow, svgWidth int, sv
|
|||||||
var maxBY int = 0
|
var maxBY int = 0
|
||||||
var minBX int = 0
|
var minBX int = 0
|
||||||
for idx, item := range inputData {
|
for idx, item := range inputData {
|
||||||
itemSize := int(float32(item.MinutesRead) * sizeRatio)
|
itemSize := int(float32(item) * sizeRatio)
|
||||||
itemY := svgHeight - itemSize
|
itemY := svgHeight - itemSize
|
||||||
lineX := (idx + 1) * blockOffset
|
lineX := (idx + 1) * blockOffset
|
||||||
barPoints = append(barPoints, SVGGraphPoint{
|
barPoints = append(barPoints, SVGGraphPoint{
|
||||||
|
33
graph/graph_test.go
Normal file
33
graph/graph_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetSVGGraphData(t *testing.T) {
|
||||||
|
inputPoints := []int64{10, 90, 50, 5, 10, 5, 70, 60, 50, 90}
|
||||||
|
svgData := GetSVGGraphData(inputPoints, 500, 100)
|
||||||
|
|
||||||
|
expect := "M 50,95 C63,95 80,50 100,50 C120,50 128,73 150,73 C172,73 180,98 200,98 C220,98 230,95 250,95 C270,95 279,98 300,98 C321,98 330,62 350,62 C370,62 380,67 400,67 C420,67 430,73 450,73 C470,73 489,50 500,50"
|
||||||
|
if svgData.BezierPath != expect {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, expect, svgData.BezierPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect = "L 500,98 L 50,98 Z"
|
||||||
|
if svgData.BezierFill != expect {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, expect, svgData.BezierFill)
|
||||||
|
}
|
||||||
|
|
||||||
|
if svgData.Width != 500 {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, 500, svgData.Width)
|
||||||
|
}
|
||||||
|
|
||||||
|
if svgData.Height != 100 {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, 100, svgData.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
if svgData.Offset != 50 {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, 50, svgData.Offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
metadata/_test_files/alice.epub
Normal file
BIN
metadata/_test_files/alice.epub
Normal file
Binary file not shown.
308
metadata/epub.go
308
metadata/epub.go
@ -1,301 +1,35 @@
|
|||||||
/*
|
|
||||||
Package epub provides basic support for reading EPUB archives.
|
|
||||||
Adapted from: https://github.com/taylorskalyo/goreader
|
|
||||||
*/
|
|
||||||
package metadata
|
package metadata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
|
||||||
"bytes"
|
|
||||||
"encoding/xml"
|
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/taylorskalyo/goreader/epub"
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
)
|
)
|
||||||
|
|
||||||
const containerPath = "META-INF/container.xml"
|
func countEPUBWords(filepath string) (int64, error) {
|
||||||
|
rc, err := epub.OpenReader(filepath)
|
||||||
var (
|
|
||||||
// ErrNoRootfile occurs when there are no rootfile entries found in
|
|
||||||
// container.xml.
|
|
||||||
ErrNoRootfile = errors.New("epub: no rootfile found in container")
|
|
||||||
|
|
||||||
// ErrBadRootfile occurs when container.xml references a rootfile that does
|
|
||||||
// not exist in the zip.
|
|
||||||
ErrBadRootfile = errors.New("epub: container references non-existent rootfile")
|
|
||||||
|
|
||||||
// ErrNoItemref occurrs when a content.opf contains a spine without any
|
|
||||||
// itemref entries.
|
|
||||||
ErrNoItemref = errors.New("epub: no itemrefs found in spine")
|
|
||||||
|
|
||||||
// ErrBadItemref occurs when an itemref entry in content.opf references an
|
|
||||||
// item that does not exist in the manifest.
|
|
||||||
ErrBadItemref = errors.New("epub: itemref references non-existent item")
|
|
||||||
|
|
||||||
// ErrBadManifest occurs when a manifest in content.opf references an item
|
|
||||||
// that does not exist in the zip.
|
|
||||||
ErrBadManifest = errors.New("epub: manifest references non-existent item")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reader represents a readable epub file.
|
|
||||||
type Reader struct {
|
|
||||||
Container
|
|
||||||
files map[string]*zip.File
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadCloser represents a readable epub file that can be closed.
|
|
||||||
type ReadCloser struct {
|
|
||||||
Reader
|
|
||||||
f *os.File
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rootfile contains the location of a content.opf package file.
|
|
||||||
type Rootfile struct {
|
|
||||||
FullPath string `xml:"full-path,attr"`
|
|
||||||
Package
|
|
||||||
}
|
|
||||||
|
|
||||||
// Container serves as a directory of Rootfiles.
|
|
||||||
type Container struct {
|
|
||||||
Rootfiles []*Rootfile `xml:"rootfiles>rootfile"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package represents an epub content.opf file.
|
|
||||||
type Package struct {
|
|
||||||
Metadata
|
|
||||||
Manifest
|
|
||||||
Spine
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata contains publishing information about the epub.
|
|
||||||
type Metadata struct {
|
|
||||||
Title string `xml:"metadata>title"`
|
|
||||||
Language string `xml:"metadata>language"`
|
|
||||||
Identifier string `xml:"metadata>idenifier"`
|
|
||||||
Creator string `xml:"metadata>creator"`
|
|
||||||
Contributor string `xml:"metadata>contributor"`
|
|
||||||
Publisher string `xml:"metadata>publisher"`
|
|
||||||
Subject string `xml:"metadata>subject"`
|
|
||||||
Description string `xml:"metadata>description"`
|
|
||||||
Event []struct {
|
|
||||||
Name string `xml:"event,attr"`
|
|
||||||
Date string `xml:",innerxml"`
|
|
||||||
} `xml:"metadata>date"`
|
|
||||||
Type string `xml:"metadata>type"`
|
|
||||||
Format string `xml:"metadata>format"`
|
|
||||||
Source string `xml:"metadata>source"`
|
|
||||||
Relation string `xml:"metadata>relation"`
|
|
||||||
Coverage string `xml:"metadata>coverage"`
|
|
||||||
Rights string `xml:"metadata>rights"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manifest lists every file that is part of the epub.
|
|
||||||
type Manifest struct {
|
|
||||||
Items []Item `xml:"manifest>item"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Item represents a file stored in the epub.
|
|
||||||
type Item struct {
|
|
||||||
ID string `xml:"id,attr"`
|
|
||||||
HREF string `xml:"href,attr"`
|
|
||||||
MediaType string `xml:"media-type,attr"`
|
|
||||||
f *zip.File
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spine defines the reading order of the epub documents.
|
|
||||||
type Spine struct {
|
|
||||||
Itemrefs []Itemref `xml:"spine>itemref"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Itemref points to an Item.
|
|
||||||
type Itemref struct {
|
|
||||||
IDREF string `xml:"idref,attr"`
|
|
||||||
*Item
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenEPUBReader will open the epub file specified by name and return a
|
|
||||||
// ReadCloser.
|
|
||||||
func OpenEPUBReader(name string) (*ReadCloser, error) {
|
|
||||||
f, err := os.Open(name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
rf := rc.Rootfiles[0]
|
||||||
|
|
||||||
rc := new(ReadCloser)
|
|
||||||
rc.f = f
|
|
||||||
|
|
||||||
fi, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
f.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
z, err := zip.NewReader(f, fi.Size())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = rc.init(z); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader returns a new Reader reading from ra, which is assumed to have the
|
|
||||||
// given size in bytes.
|
|
||||||
func NewReader(ra io.ReaderAt, size int64) (*Reader, error) {
|
|
||||||
z, err := zip.NewReader(ra, size)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := new(Reader)
|
|
||||||
if err = r.init(z); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) init(z *zip.Reader) error {
|
|
||||||
// Create a file lookup table
|
|
||||||
r.files = make(map[string]*zip.File)
|
|
||||||
for _, f := range z.File {
|
|
||||||
r.files[f.Name] = f
|
|
||||||
}
|
|
||||||
|
|
||||||
err := r.setContainer()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = r.setPackages()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = r.setItems()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setContainer unmarshals the epub's container.xml file.
|
|
||||||
func (r *Reader) setContainer() error {
|
|
||||||
f, err := r.files[containerPath].Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
_, err = io.Copy(&b, f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = xml.Unmarshal(b.Bytes(), &r.Container)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.Container.Rootfiles) < 1 {
|
|
||||||
return ErrNoRootfile
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setPackages unmarshal's each of the epub's content.opf files.
|
|
||||||
func (r *Reader) setPackages() error {
|
|
||||||
for _, rf := range r.Container.Rootfiles {
|
|
||||||
if r.files[rf.FullPath] == nil {
|
|
||||||
return ErrBadRootfile
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := r.files[rf.FullPath].Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
_, err = io.Copy(&b, f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = xml.Unmarshal(b.Bytes(), &rf.Package)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setItems associates Itemrefs with their respective Item and Items with
|
|
||||||
// their zip.File.
|
|
||||||
func (r *Reader) setItems() error {
|
|
||||||
itemrefCount := 0
|
|
||||||
for _, rf := range r.Container.Rootfiles {
|
|
||||||
itemMap := make(map[string]*Item)
|
|
||||||
for i := range rf.Manifest.Items {
|
|
||||||
item := &rf.Manifest.Items[i]
|
|
||||||
itemMap[item.ID] = item
|
|
||||||
|
|
||||||
abs := path.Join(path.Dir(rf.FullPath), item.HREF)
|
|
||||||
item.f = r.files[abs]
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range rf.Spine.Itemrefs {
|
|
||||||
itemref := &rf.Spine.Itemrefs[i]
|
|
||||||
itemref.Item = itemMap[itemref.IDREF]
|
|
||||||
if itemref.Item == nil {
|
|
||||||
return ErrBadItemref
|
|
||||||
}
|
|
||||||
}
|
|
||||||
itemrefCount += len(rf.Spine.Itemrefs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if itemrefCount < 1 {
|
|
||||||
return ErrNoItemref
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open returns a ReadCloser that provides access to the Items's contents.
|
|
||||||
// Multiple items may be read concurrently.
|
|
||||||
func (item *Item) Open() (r io.ReadCloser, err error) {
|
|
||||||
if item.f == nil {
|
|
||||||
return nil, ErrBadManifest
|
|
||||||
}
|
|
||||||
|
|
||||||
return item.f.Open()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the epub file, rendering it unusable for I/O.
|
|
||||||
func (rc *ReadCloser) Close() {
|
|
||||||
rc.f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hehe
|
|
||||||
func (rf *Rootfile) CountWords() int64 {
|
|
||||||
var completeCount int64
|
var completeCount int64
|
||||||
for _, item := range rf.Spine.Itemrefs {
|
for _, item := range rf.Spine.Itemrefs {
|
||||||
f, _ := item.Open()
|
f, _ := item.Open()
|
||||||
tokenizer := html.NewTokenizer(f)
|
tokenizer := html.NewTokenizer(f)
|
||||||
completeCount = completeCount + countWords(*tokenizer)
|
newCount, err := countTokenizerWords(*tokenizer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
completeCount = completeCount + newCount
|
||||||
}
|
}
|
||||||
|
|
||||||
return completeCount
|
return completeCount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func countWords(tokenizer html.Tokenizer) int64 {
|
func countTokenizerWords(tokenizer html.Tokenizer) (int64, error) {
|
||||||
var err error
|
var err error
|
||||||
var totalWords int64
|
var totalWords int64
|
||||||
for {
|
for {
|
||||||
@ -308,23 +42,9 @@ func countWords(tokenizer html.Tokenizer) int64 {
|
|||||||
err = tokenizer.Err()
|
err = tokenizer.Err()
|
||||||
}
|
}
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return totalWords
|
return totalWords, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return 0
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func main() {
|
|
||||||
rc, err := OpenEPUBReader("test.epub")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rf := rc.Rootfiles[0]
|
|
||||||
|
|
||||||
totalWords := rf.CountWords()
|
|
||||||
log.Info("WOAH WORDS:", totalWords)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
76
metadata/integrations_test.go
Normal file
76
metadata/integrations_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGBooksGBIDMetadata(t *testing.T) {
|
||||||
|
GBID := "ZxwpakTv_MIC"
|
||||||
|
metadataResp, err := getGBooksMetadata(MetadataInfo{
|
||||||
|
ID: &GBID,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(metadataResp) != 1 {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v, Error: %v`, 1, len(metadataResp), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mResult := metadataResp[0]
|
||||||
|
validateResult(&mResult, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGBooksISBNQuery(t *testing.T) {
|
||||||
|
ISBN10 := "1877527815"
|
||||||
|
metadataResp, err := getGBooksMetadata(MetadataInfo{
|
||||||
|
ISBN10: &ISBN10,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(metadataResp) != 1 {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v, Error: %v`, 1, len(metadataResp), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mResult := metadataResp[0]
|
||||||
|
validateResult(&mResult, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGBooksTitleQuery(t *testing.T) {
|
||||||
|
title := "Alice in Wonderland"
|
||||||
|
metadataResp, err := getGBooksMetadata(MetadataInfo{
|
||||||
|
Title: &title,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(metadataResp) == 0 {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v, Error: %v`, "> 0", len(metadataResp), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mResult := metadataResp[0]
|
||||||
|
validateResult(&mResult, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateResult(m *MetadataInfo, t *testing.T) {
|
||||||
|
expect := "Lewis Carroll"
|
||||||
|
if *m.Author != expect {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, expect, *m.Author)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect = "Alice in Wonderland"
|
||||||
|
if *m.Title != expect {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, expect, *m.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect = "Alice in Wonderland (also known as Alice's Adventures in Wonderland), from 1865, is the peculiar and imaginative tale of a girl who falls down a rabbit-hole into a bizarre world of eccentric and unusual creatures. Lewis Carroll's prominent example of the genre of \"literary nonsense\" has endured in popularity with its clever way of playing with logic and a narrative structure that has influence generations of fiction writing."
|
||||||
|
if *m.Description != expect {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, expect, *m.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect = "1877527815"
|
||||||
|
if *m.ISBN10 != expect {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, expect, *m.ISBN10)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect = "9781877527814"
|
||||||
|
if *m.ISBN13 != expect {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v`, expect, *m.ISBN13)
|
||||||
|
}
|
||||||
|
}
|
@ -58,13 +58,10 @@ func GetWordCount(filepath string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fileExtension := fileMime.Extension(); fileExtension == ".epub" {
|
if fileExtension := fileMime.Extension(); fileExtension == ".epub" {
|
||||||
rc, err := OpenEPUBReader(filepath)
|
totalWords, err := countEPUBWords(filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rf := rc.Rootfiles[0]
|
|
||||||
totalWords := rf.CountWords()
|
|
||||||
return totalWords, nil
|
return totalWords, nil
|
||||||
} else {
|
} else {
|
||||||
return 0, errors.New("Invalid Extension")
|
return 0, errors.New("Invalid Extension")
|
||||||
|
14
metadata/metadata_test.go
Normal file
14
metadata/metadata_test.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetWordCount(t *testing.T) {
|
||||||
|
var want int64 = 30477
|
||||||
|
wordCount, err := countEPUBWords("./_test_files/alice.epub")
|
||||||
|
|
||||||
|
if wordCount != want {
|
||||||
|
t.Fatalf(`Expected: %v, Got: %v, Error: %v`, want, wordCount, err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user