Significantly simplified adding new Database Tables
This commit is contained in:
@@ -33,15 +33,17 @@ func main() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
port := 8080
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(300 * time.Millisecond)
|
||||||
err := open("http://localhost:8000")
|
err := open(fmt.Sprintf("http://localhost:%d", port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = s.Start()
|
err = s.Start(port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,61 +2,56 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Chapter struct {
|
type Chapter struct {
|
||||||
Id int
|
Id int
|
||||||
Manga *Manga
|
MangaId int
|
||||||
Url string
|
Url string
|
||||||
Name string
|
Name string
|
||||||
Number int
|
Number int
|
||||||
TimeStampUnix int64
|
TimeStampUnix int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapterTable[K comparable] struct {
|
func NewChapter(id int, mangaId int, url string, name string, number int, timeStampUnix int64) Chapter {
|
||||||
mutex sync.Mutex
|
return Chapter{
|
||||||
chapters map[K]Chapter
|
Id: id,
|
||||||
updated map[K]DbStatus
|
MangaId: mangaId,
|
||||||
}
|
Url: url,
|
||||||
|
Name: name,
|
||||||
func (c *ChapterTable[K]) Get(key K) (Chapter, bool) {
|
Number: number,
|
||||||
c.mutex.Lock()
|
TimeStampUnix: timeStampUnix,
|
||||||
defer c.mutex.Unlock()
|
|
||||||
val, ok := c.chapters[key]
|
|
||||||
return val, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ChapterTable[K]) Set(key K, new Chapter) {
|
|
||||||
c.mutex.Lock()
|
|
||||||
defer c.mutex.Unlock()
|
|
||||||
val, ok := c.updated[key]
|
|
||||||
if ok && val == Loaded {
|
|
||||||
c.updated[key] = Updated
|
|
||||||
} else {
|
|
||||||
c.updated[key] = New
|
|
||||||
}
|
}
|
||||||
c.chapters[key] = new
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChapterTable[K]) All() []Chapter {
|
func updateChapter(db *sql.DB, c *Chapter) error {
|
||||||
c.mutex.Lock()
|
_, err := db.Exec("UPDATE Chapter set Name = ?, Url = ?, Number = ?, TimeStampUnixEpoch = ? where ID = ?", c.Name, c.Url, c.Number, c.TimeStampUnix, c.Id)
|
||||||
defer c.mutex.Unlock()
|
return err
|
||||||
res := make([]Chapter, len(c.chapters))
|
}
|
||||||
counter := 0
|
|
||||||
for _, chapter := range c.chapters {
|
func insertChapter(db *sql.DB, c *Chapter) error {
|
||||||
res[counter] = chapter
|
_, err := db.Exec("INSERT INTO Chapter(ID, MangaID, Url, Name, Number, TimeStampUnixEpoch) VALUES (?, ?, ?, ?, ?, ?)", c.Id, c.MangaId, c.Url, c.Name, c.Number, c.TimeStampUnix)
|
||||||
counter++
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadChapters(db *sql.DB) (map[int]Chapter, error) {
|
||||||
|
rows, err := db.Query("SELECT Id, MangaID, Url, Name, Number, TimeStampUnixEpoch FROM Chapter")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return res
|
res := make(map[int]Chapter)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
chapter := Chapter{}
|
||||||
|
if err = rows.Scan(&chapter.Id, &chapter.MangaId, &chapter.Url, &chapter.Name, &chapter.Number, &chapter.TimeStampUnix); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res[chapter.Id] = chapter
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChapterTable[K]) Save(db *sql.DB) error {
|
func deleteChapter(db *sql.DB, key int) error {
|
||||||
//TODO implement me
|
_, err := db.Exec("DELETE from Manga where ID = ?", key)
|
||||||
panic("implement me")
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ChapterTable[K]) Load(db *sql.DB) error {
|
|
||||||
//TODO implement me
|
|
||||||
panic("implement me")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,25 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
ConnectionString string
|
ConnectionString string
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
|
Mangas DbTable[int, Manga]
|
||||||
Rw *sync.Mutex
|
Chapters DbTable[int, Chapter]
|
||||||
Mangas map[int]*Manga
|
|
||||||
Chapters map[int]*Chapter
|
|
||||||
|
|
||||||
CreateIfNotExists bool
|
CreateIfNotExists bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDatabase(connectionString string, createIfNotExists bool) Manager {
|
func NewDatabase(connectionString string, createIfNotExists bool) Manager {
|
||||||
return Manager{
|
return Manager{
|
||||||
ConnectionString: connectionString,
|
ConnectionString: connectionString,
|
||||||
Rw: &sync.Mutex{},
|
db: nil,
|
||||||
Mangas: make(map[int]*Manga),
|
Mangas: NewDbTable[int, Manga](updateManga, insertManga, loadMangas, deleteManga),
|
||||||
Chapters: make(map[int]*Chapter),
|
Chapters: NewDbTable[int, Chapter](updateChapter, insertChapter, loadChapters, deleteChapter),
|
||||||
CreateIfNotExists: createIfNotExists,
|
CreateIfNotExists: createIfNotExists,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,103 +46,21 @@ func (dbMgr *Manager) Close() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dbMgr.Mangas = nil
|
|
||||||
dbMgr.Chapters = nil
|
|
||||||
dbMgr.db = nil
|
dbMgr.db = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbMgr *Manager) Delete(mangaId int) error {
|
func (dbMgr *Manager) Delete(mangaId int) error {
|
||||||
db := dbMgr.db
|
dbMgr.Mangas.Get(mangaId)
|
||||||
fmt.Println("Locking Rw in database.go:84")
|
err := dbMgr.Mangas.Delete(dbMgr.db, mangaId)
|
||||||
dbMgr.Rw.Lock()
|
|
||||||
defer func() {
|
|
||||||
fmt.Println("Unlocking Rw in database.go:87")
|
|
||||||
dbMgr.Rw.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, err := db.Exec("DELETE from Chapter where MangaID = ?", mangaId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Exec("DELETE from Manga where ID = ?", mangaId)
|
chapters := dbMgr.Chapters.All()
|
||||||
if err != nil {
|
for i, chapter := range chapters {
|
||||||
return err
|
if chapter.MangaId == mangaId {
|
||||||
}
|
err := dbMgr.Chapters.Delete(dbMgr.db, i)
|
||||||
|
|
||||||
for i, chapter := range dbMgr.Chapters {
|
|
||||||
if chapter.Manga.Id == mangaId {
|
|
||||||
delete(dbMgr.Chapters, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(dbMgr.Mangas, mangaId)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbMgr *Manager) Save() error {
|
|
||||||
db := dbMgr.db
|
|
||||||
|
|
||||||
fmt.Println("Locking Rw in database.go:113")
|
|
||||||
dbMgr.Rw.Lock()
|
|
||||||
defer func() {
|
|
||||||
fmt.Println("Unlocking Rw in database.go:116")
|
|
||||||
dbMgr.Rw.Unlock()
|
|
||||||
}()
|
|
||||||
for _, m := range dbMgr.Mangas {
|
|
||||||
count := 0
|
|
||||||
err := db.QueryRow("SELECT COUNT(*) FROM Manga where ID = ?", m.Id).Scan(&count)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if count == 0 {
|
|
||||||
if m.Thumbnail != nil {
|
|
||||||
_, err := db.Exec("INSERT INTO Manga(ID, Title, TimeStampUnixEpoch, Thumbnail, LatestAvailableChapter) values(?, ?, ?, ?, ?)", m.Id, m.Title, m.TimeStampUnix, m.Thumbnail.Bytes(), m.LastChapterNum)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err := db.Exec("INSERT INTO Manga(ID, Title, TimeStampUnixEpoch, LatestAvailableChapter) values(?, ?, ?, ?)", m.Id, m.Title, m.TimeStampUnix, m.LastChapterNum)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tSet := 0
|
|
||||||
err := db.QueryRow("SELECT COUNT(*) from Manga where ID = ? and Thumbnail IS NOT NULL", m.Id).Scan(&tSet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tSet != 0 {
|
|
||||||
_, err = db.Exec("UPDATE Manga set Title = ?, TimeStampUnixEpoch = ?, LatestAvailableChapter = ? WHERE ID = ?", m.Title, m.TimeStampUnix, m.LastChapterNum, m.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = db.Exec("UPDATE Manga set Title = ?, TimeStampUnixEpoch = ?, Thumbnail = ?, LatestAvailableChapter = ? WHERE ID = ?", m.Title, m.TimeStampUnix, m.Thumbnail.Bytes(), m.LastChapterNum, m.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range dbMgr.Chapters {
|
|
||||||
count := 0
|
|
||||||
err := db.QueryRow("SELECT COUNT(*) FROM Chapter where ID = ?", c.Id).Scan(&count)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if count == 0 {
|
|
||||||
_, err := db.Exec("INSERT INTO Chapter(ID, MangaID, Url, Name, Number, TimeStampUnixEpoch) VALUES (?, ?, ?, ?, ?, ?)", c.Id, c.Manga.Id, c.Url, c.Name, c.Number, c.TimeStampUnix)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = db.Exec("UPDATE Chapter set Name = ?, Url = ?, Number = ?, TimeStampUnixEpoch = ? where ID = ?", c.Name, c.Url, c.Number, c.TimeStampUnix, c.Id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -159,6 +70,15 @@ func (dbMgr *Manager) Save() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dbMgr *Manager) Save() error {
|
||||||
|
err := dbMgr.Mangas.Save(dbMgr.db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbMgr.Chapters.Save(dbMgr.db)
|
||||||
|
}
|
||||||
|
|
||||||
//go:embed createDb.sql
|
//go:embed createDb.sql
|
||||||
var createSql string
|
var createSql string
|
||||||
|
|
||||||
@@ -168,39 +88,15 @@ func (dbMgr *Manager) createDatabaseIfNotExists() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dbMgr *Manager) load() error {
|
func (dbMgr *Manager) load() error {
|
||||||
db := dbMgr.db
|
err := dbMgr.Chapters.Load(dbMgr.db)
|
||||||
|
|
||||||
fmt.Println("Locking Rw in database.go:180")
|
|
||||||
dbMgr.Rw.Lock()
|
|
||||||
defer func() {
|
|
||||||
fmt.Println("Unlocking Rw in database.go:183")
|
|
||||||
dbMgr.Rw.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
rows, err := db.Query("SELECT Id, Title, TimeStampUnixEpoch, Thumbnail, LatestAvailableChapter FROM Manga")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for rows.Next() {
|
err = dbMgr.Mangas.Load(dbMgr.db)
|
||||||
manga := Manga{}
|
if err != nil {
|
||||||
var thumbnail []byte
|
return err
|
||||||
if err = rows.Scan(&manga.Id, &manga.Title, &manga.TimeStampUnix, &thumbnail, &manga.LastChapterNum); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(thumbnail) != 0 {
|
|
||||||
manga.Thumbnail = bytes.NewBuffer(thumbnail)
|
|
||||||
}
|
|
||||||
|
|
||||||
latestChapter := db.QueryRow("SELECT Id, Url, Name, Number, TimeStampUnixEpoch FROM Chapter where MangaID = ? ORDER BY TimeStampUnixEpoch desc LIMIT 1", manga.Id)
|
|
||||||
chapter := Chapter{}
|
|
||||||
if err = latestChapter.Scan(&chapter.Id, &chapter.Url, &chapter.Name, &chapter.Number, &chapter.TimeStampUnix); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chapter.Manga = &manga
|
|
||||||
manga.LatestChapter = &chapter
|
|
||||||
dbMgr.Chapters[chapter.Id] = &chapter
|
|
||||||
dbMgr.Mangas[manga.Id] = &manga
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cmp"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"sync"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manga struct {
|
type Manga struct {
|
||||||
@@ -12,73 +13,31 @@ type Manga struct {
|
|||||||
TimeStampUnix int64
|
TimeStampUnix int64
|
||||||
Thumbnail *bytes.Buffer
|
Thumbnail *bytes.Buffer
|
||||||
LastChapterNum int
|
LastChapterNum int
|
||||||
|
|
||||||
// Not in DB
|
|
||||||
LatestChapter *Chapter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MangaTable[K comparable] struct {
|
func NewManga(id int, title string, timeStampUnix int64) Manga {
|
||||||
mutex sync.Mutex
|
return Manga{
|
||||||
mangas map[K]Manga
|
Id: id,
|
||||||
updated map[K]DbStatus
|
Title: title,
|
||||||
}
|
TimeStampUnix: timeStampUnix,
|
||||||
|
|
||||||
func (m *MangaTable[K]) Get(key K) (Manga, bool) {
|
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
val, ok := m.mangas[key]
|
|
||||||
return val, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MangaTable[K]) Set(key K, new Manga) {
|
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
val, ok := m.updated[key]
|
|
||||||
if ok && val == Loaded {
|
|
||||||
m.updated[key] = Updated
|
|
||||||
} else {
|
|
||||||
m.updated[key] = New
|
|
||||||
}
|
}
|
||||||
m.mangas[key] = new
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MangaTable[K]) All() []Manga {
|
// GetLatestChapter TODO: Cache this somehow
|
||||||
m.mutex.Lock()
|
func (m *Manga) GetLatestChapter(chapters *DbTable[int, Chapter]) (*Chapter, bool) {
|
||||||
defer m.mutex.Unlock()
|
c := chapters.All()
|
||||||
res := make([]Manga, len(m.mangas))
|
|
||||||
counter := 0
|
|
||||||
for _, manga := range m.mangas {
|
|
||||||
res[counter] = manga
|
|
||||||
counter++
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MangaTable[K]) Save(db *sql.DB) error {
|
slices.SortStableFunc(c, func(a, b Chapter) int {
|
||||||
m.mutex.Lock()
|
return cmp.Compare(b.TimeStampUnix, a.TimeStampUnix)
|
||||||
defer m.mutex.Unlock()
|
})
|
||||||
for k, status := range m.updated {
|
|
||||||
if status == Loaded {
|
for _, chapter := range c {
|
||||||
continue
|
if chapter.MangaId == m.Id {
|
||||||
} else if status == Updated {
|
return &chapter, true
|
||||||
manga := m.mangas[k]
|
|
||||||
err := updateManga(db, &manga)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
manga := m.mangas[k]
|
|
||||||
err := insertManga(db, &manga)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MangaTable[K]) Load(db *sql.DB) error {
|
return nil, false
|
||||||
panic("")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateManga(db *sql.DB, m *Manga) error {
|
func updateManga(db *sql.DB, m *Manga) error {
|
||||||
@@ -92,3 +51,32 @@ func insertManga(db *sql.DB, manga *Manga) error {
|
|||||||
_, err := db.Exec(cmd, manga.Id, manga.Title, manga.TimeStampUnix, manga.Thumbnail.Bytes(), manga.LastChapterNum)
|
_, err := db.Exec(cmd, manga.Id, manga.Title, manga.TimeStampUnix, manga.Thumbnail.Bytes(), manga.LastChapterNum)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadMangas(db *sql.DB) (map[int]Manga, error) {
|
||||||
|
rows, err := db.Query("SELECT Id, Title, TimeStampUnixEpoch, Thumbnail, LatestAvailableChapter FROM Manga")
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res := make(map[int]Manga)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
manga := Manga{}
|
||||||
|
var thumbnail []byte
|
||||||
|
if err = rows.Scan(&manga.Id, &manga.Title, &manga.TimeStampUnix, &thumbnail, &manga.LastChapterNum); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(thumbnail) != 0 {
|
||||||
|
manga.Thumbnail = bytes.NewBuffer(thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
res[manga.Id] = manga
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteManga(db *sql.DB, key int) error {
|
||||||
|
_, err := db.Exec("DELETE from Chapter where ID = ?", key)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import "database/sql"
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
type DbStatus int
|
type DbStatus int
|
||||||
|
|
||||||
@@ -10,11 +13,121 @@ const (
|
|||||||
Updated
|
Updated
|
||||||
)
|
)
|
||||||
|
|
||||||
// Table TODO: This Could probably be a generic instead of interface / both
|
//type Table[K comparable, T any] interface {
|
||||||
type Table[K comparable, T any] interface {
|
// Get(key K) (T, bool)
|
||||||
Get(key K) (T, bool)
|
// Set(key K, new T)
|
||||||
Set(key K, new T)
|
// All() []T
|
||||||
All() []T
|
// Delete(key K) error
|
||||||
Save(db *sql.DB) error
|
// Save(db *sql.DB) error
|
||||||
Load(db *sql.DB) error
|
// Load(db *sql.DB) error
|
||||||
|
// Connect(key K, value *any) bool
|
||||||
|
//}
|
||||||
|
|
||||||
|
type DbTable[K comparable, T any] struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
items map[K]T
|
||||||
|
updated map[K]DbStatus
|
||||||
|
updateFunc func(db *sql.DB, value *T) error
|
||||||
|
insertFunc func(db *sql.DB, value *T) error
|
||||||
|
loadFunc func(db *sql.DB) (map[K]T, error)
|
||||||
|
deleteFunc func(db *sql.DB, key K) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDbTable[K comparable, T any](updateFunc func(db *sql.DB, value *T) error,
|
||||||
|
insertFunc func(db *sql.DB, value *T) error,
|
||||||
|
loadFunc func(db *sql.DB) (map[K]T, error),
|
||||||
|
deleteFunc func(db *sql.DB, key K) error,
|
||||||
|
) DbTable[K, T] {
|
||||||
|
return DbTable[K, T]{
|
||||||
|
mutex: sync.Mutex{},
|
||||||
|
items: make(map[K]T),
|
||||||
|
updated: make(map[K]DbStatus),
|
||||||
|
updateFunc: updateFunc,
|
||||||
|
insertFunc: insertFunc,
|
||||||
|
loadFunc: loadFunc,
|
||||||
|
deleteFunc: deleteFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTable[K, T]) Get(key K) (T, bool) {
|
||||||
|
d.mutex.Lock()
|
||||||
|
defer d.mutex.Unlock()
|
||||||
|
val, ok := d.items[key]
|
||||||
|
return val, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRef unsafe
|
||||||
|
func (d *DbTable[K, T]) getRef(key K) (*T, bool) {
|
||||||
|
d.mutex.Lock()
|
||||||
|
defer d.mutex.Unlock()
|
||||||
|
val, ok := d.items[key]
|
||||||
|
return &val, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTable[K, T]) Set(key K, new T) {
|
||||||
|
d.mutex.Lock()
|
||||||
|
defer d.mutex.Unlock()
|
||||||
|
val, ok := d.updated[key]
|
||||||
|
if ok && val == Loaded || val == Updated {
|
||||||
|
d.updated[key] = Updated
|
||||||
|
} else {
|
||||||
|
d.updated[key] = New
|
||||||
|
}
|
||||||
|
d.items[key] = new
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTable[K, T]) All() []T {
|
||||||
|
d.mutex.Lock()
|
||||||
|
defer d.mutex.Unlock()
|
||||||
|
res := make([]T, len(d.items))
|
||||||
|
counter := 0
|
||||||
|
for _, manga := range d.items {
|
||||||
|
res[counter] = manga
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTable[K, T]) Delete(db *sql.DB, key K) error {
|
||||||
|
d.mutex.Lock()
|
||||||
|
defer d.mutex.Unlock()
|
||||||
|
delete(d.items, key)
|
||||||
|
return d.deleteFunc(db, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTable[K, T]) Save(db *sql.DB) error {
|
||||||
|
d.mutex.Lock()
|
||||||
|
defer d.mutex.Unlock()
|
||||||
|
for k, status := range d.updated {
|
||||||
|
if status == Loaded {
|
||||||
|
continue
|
||||||
|
} else if status == Updated {
|
||||||
|
item := d.items[k]
|
||||||
|
err := d.updateFunc(db, &item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item := d.items[k]
|
||||||
|
err := d.insertFunc(db, &item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTable[K, T]) Load(db *sql.DB) error {
|
||||||
|
d.mutex.Lock()
|
||||||
|
defer d.mutex.Unlock()
|
||||||
|
res, err := d.loadFunc(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.items = res
|
||||||
|
for k := range d.items {
|
||||||
|
d.updated[k] = Loaded
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,15 +35,7 @@ func (s *Server) HandleNew(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (s *Server) HandleMenu(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) HandleMenu(w http.ResponseWriter, r *http.Request) {
|
||||||
tmpl := template.Must(view.GetViewTemplate(view.Menu))
|
tmpl := template.Must(view.GetViewTemplate(view.Menu))
|
||||||
|
all := s.DbMgr.Mangas.All()
|
||||||
fmt.Println("Locking Rw in handler.go:43")
|
|
||||||
s.DbMgr.Rw.Lock()
|
|
||||||
defer func() {
|
|
||||||
fmt.Println("Unlocking Rw in handler.go:46")
|
|
||||||
s.DbMgr.Rw.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
all := s.DbMgr.Mangas
|
|
||||||
l := len(all)
|
l := len(all)
|
||||||
mangaViewModels := make([]view.MangaViewModel, l)
|
mangaViewModels := make([]view.MangaViewModel, l)
|
||||||
counter := 0
|
counter := 0
|
||||||
@@ -59,12 +51,14 @@ func (s *Server) HandleMenu(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
t1 := time.Now().UnixNano()
|
t1 := time.Now().UnixNano()
|
||||||
|
|
||||||
thumbnail, err := s.LoadThumbnail(manga)
|
thumbnail, updated, err := s.LoadThumbnail(&manga)
|
||||||
//TODO: Add default picture instead of not showing Manga at all
|
//TODO: Add default picture instead of not showing Manga at all
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
manga.Thumbnail = s.ImageBuffers[thumbnail]
|
if updated {
|
||||||
|
s.DbMgr.Mangas.Set(manga.Id, manga)
|
||||||
|
}
|
||||||
|
|
||||||
t2 := time.Now().UnixNano()
|
t2 := time.Now().UnixNano()
|
||||||
|
|
||||||
@@ -75,24 +69,32 @@ func (s *Server) HandleMenu(w http.ResponseWriter, r *http.Request) {
|
|||||||
// This is very slow
|
// This is very slow
|
||||||
// TODO: put this into own Method
|
// TODO: put this into own Method
|
||||||
if manga.LastChapterNum == 0 {
|
if manga.LastChapterNum == 0 {
|
||||||
err := s.UpdateLatestAvailableChapter(manga)
|
err, updated := s.UpdateLatestAvailableChapter(&manga)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
if updated {
|
||||||
|
s.DbMgr.Mangas.Set(manga.Id, manga)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t2 = time.Now().UnixNano()
|
t2 = time.Now().UnixNano()
|
||||||
|
|
||||||
titNs += t2 - t1
|
titNs += t2 - t1
|
||||||
|
|
||||||
|
latestChapter, ok := manga.GetLatestChapter(&s.DbMgr.Chapters)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
mangaViewModels[counter] = view.MangaViewModel{
|
mangaViewModels[counter] = view.MangaViewModel{
|
||||||
ID: manga.Id,
|
ID: manga.Id,
|
||||||
Title: title,
|
Title: title,
|
||||||
Number: manga.LatestChapter.Number,
|
Number: latestChapter.Number,
|
||||||
LastNumber: manga.LastChapterNum,
|
LastNumber: manga.LastChapterNum,
|
||||||
// I Hate this time Format... 15 = hh, 04 = mm, 02 = DD, 01 = MM, 06 == YY
|
// I Hate this time Format... 15 = hh, 04 = mm, 02 = DD, 01 = MM, 06 == YY
|
||||||
LastTime: time.Unix(manga.TimeStampUnix, 0).Format("15:04 (02-01-06)"),
|
LastTime: time.Unix(manga.TimeStampUnix, 0).Format("15:04 (02-01-06)"),
|
||||||
Url: manga.LatestChapter.Url,
|
Url: latestChapter.Url,
|
||||||
ThumbnailUrl: thumbnail,
|
ThumbnailUrl: thumbnail,
|
||||||
}
|
}
|
||||||
counter++
|
counter++
|
||||||
@@ -201,56 +203,37 @@ func (s *Server) HandleExit(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (s *Server) HandleCurrent(w http.ResponseWriter, _ *http.Request) {
|
func (s *Server) HandleCurrent(w http.ResponseWriter, _ *http.Request) {
|
||||||
tmpl := template.Must(view.GetViewTemplate(view.Viewer))
|
tmpl := template.Must(view.GetViewTemplate(view.Viewer))
|
||||||
|
|
||||||
fmt.Println("Locking Rw in handler.go:125")
|
|
||||||
s.DbMgr.Rw.Lock()
|
|
||||||
defer func() {
|
|
||||||
fmt.Println("Unlocking Rw in handler.go:128")
|
|
||||||
s.DbMgr.Rw.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
mangaId, chapterId, err := s.Provider.GetTitleIdAndChapterId(s.CurrSubUrl)
|
mangaId, chapterId, err := s.Provider.GetTitleIdAndChapterId(s.CurrSubUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
} else {
|
} else {
|
||||||
title, chapter, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
title, chapterName, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
} else {
|
} else {
|
||||||
var manga *database.Manga
|
manga, ok := s.DbMgr.Mangas.Get(mangaId)
|
||||||
if s.DbMgr.Mangas[mangaId] == nil {
|
if !ok {
|
||||||
manga = &database.Manga{
|
manga = database.NewManga(mangaId, title, time.Now().Unix())
|
||||||
Id: mangaId,
|
|
||||||
Title: title,
|
|
||||||
TimeStampUnix: time.Now().Unix(),
|
|
||||||
}
|
|
||||||
s.DbMgr.Mangas[mangaId] = manga
|
|
||||||
} else {
|
} else {
|
||||||
manga = s.DbMgr.Mangas[mangaId]
|
manga.TimeStampUnix = time.Now().Unix()
|
||||||
s.DbMgr.Mangas[mangaId].TimeStampUnix = time.Now().Unix()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.DbMgr.Chapters[chapterId] == nil {
|
chapter, ok := s.DbMgr.Chapters.Get(chapterId)
|
||||||
chapterNumberStr := strings.Replace(chapter, "ch_", "", 1)
|
if !ok {
|
||||||
|
chapterNumberStr := strings.Replace(chapterName, "ch_", "", 1)
|
||||||
number, err := strconv.Atoi(chapterNumberStr)
|
number, err := strconv.Atoi(chapterNumberStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
number = 0
|
number = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
s.DbMgr.Chapters[chapterId] = &database.Chapter{
|
chapter = database.NewChapter(chapterId, manga.Id, s.CurrSubUrl, chapterName, number, time.Now().Unix())
|
||||||
Id: chapterId,
|
|
||||||
Manga: manga,
|
|
||||||
Url: s.CurrSubUrl,
|
|
||||||
Name: chapter,
|
|
||||||
Number: number,
|
|
||||||
TimeStampUnix: time.Now().Unix(),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
s.DbMgr.Chapters[chapterId].TimeStampUnix = time.Now().Unix()
|
chapter.TimeStampUnix = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
s.DbMgr.Mangas[mangaId].LatestChapter = s.DbMgr.Chapters[chapterId]
|
s.DbMgr.Chapters.Set(chapterId, chapter)
|
||||||
|
s.DbMgr.Mangas.Set(mangaId, manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func New(provider provider.Provider, db *database.Manager) *Server {
|
|||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start(port int) error {
|
||||||
http.HandleFunc("/", s.HandleMenu)
|
http.HandleFunc("/", s.HandleMenu)
|
||||||
http.HandleFunc("/new/", s.HandleNewQuery)
|
http.HandleFunc("/new/", s.HandleNewQuery)
|
||||||
http.HandleFunc("/new/title/{title}/{chapter}", s.HandleNew)
|
http.HandleFunc("/new/title/{title}/{chapter}", s.HandleNew)
|
||||||
@@ -62,33 +62,35 @@ func (s *Server) Start() error {
|
|||||||
// Update Latest Chapter every 5 Minutes
|
// Update Latest Chapter every 5 Minutes
|
||||||
go func(s *Server) {
|
go func(s *Server) {
|
||||||
time.AfterFunc(time.Second*10, func() {
|
time.AfterFunc(time.Second*10, func() {
|
||||||
s.DbMgr.Rw.Lock()
|
for _, m := range s.DbMgr.Mangas.All() {
|
||||||
for _, m := range s.DbMgr.Mangas {
|
err, updated := s.UpdateLatestAvailableChapter(&m)
|
||||||
err := s.UpdateLatestAvailableChapter(m)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
if updated {
|
||||||
|
s.DbMgr.Mangas.Set(m.Id, m)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.DbMgr.Rw.Unlock()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Minute * 5):
|
case <-time.After(time.Minute * 5):
|
||||||
s.DbMgr.Rw.Lock()
|
for _, m := range s.DbMgr.Mangas.All() {
|
||||||
for _, m := range s.DbMgr.Mangas {
|
err, updated := s.UpdateLatestAvailableChapter(&m)
|
||||||
err := s.UpdateLatestAvailableChapter(m)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
if updated {
|
||||||
|
s.DbMgr.Mangas.Set(m.Id, m)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.DbMgr.Rw.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(s)
|
}(s)
|
||||||
|
|
||||||
fmt.Println("Server starting...")
|
fmt.Println("Server starting...")
|
||||||
err := http.ListenAndServe(":8000", nil)
|
err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,56 +205,60 @@ func (s *Server) LoadCurr() {
|
|||||||
fmt.Println("Loaded current")
|
fmt.Println("Loaded current")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) UpdateLatestAvailableChapter(manga *database.Manga) error {
|
func (s *Server) UpdateLatestAvailableChapter(manga *database.Manga) (error, bool) {
|
||||||
fmt.Printf("Updating Manga: %s\n", manga.Title)
|
fmt.Printf("Updating Manga: %s\n", manga.Title)
|
||||||
|
|
||||||
l, err := s.Provider.GetChapterList("/title/" + strconv.Itoa(manga.Id))
|
l, err := s.Provider.GetChapterList("/title/" + strconv.Itoa(manga.Id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
le := len(l)
|
le := len(l)
|
||||||
_, c, err := s.Provider.GetTitleAndChapter(l[le-1])
|
_, c, err := s.Provider.GetTitleAndChapter(l[le-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
chapterNumberStr := strings.Replace(c, "ch_", "", 1)
|
chapterNumberStr := strings.Replace(c, "ch_", "", 1)
|
||||||
|
|
||||||
i, err := strconv.Atoi(chapterNumberStr)
|
i, err := strconv.Atoi(chapterNumberStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
manga.LastChapterNum = i
|
if manga.LastChapterNum == i {
|
||||||
return nil
|
return nil, false
|
||||||
|
} else {
|
||||||
|
manga.LastChapterNum = i
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) LoadThumbnail(manga *database.Manga) (path string, err error) {
|
func (s *Server) LoadThumbnail(manga *database.Manga) (path string, updated bool, err error) {
|
||||||
strId := strconv.Itoa(manga.Id)
|
strId := strconv.Itoa(manga.Id)
|
||||||
|
|
||||||
s.Mutex.Lock()
|
s.Mutex.Lock()
|
||||||
defer s.Mutex.Unlock()
|
defer s.Mutex.Unlock()
|
||||||
if s.ImageBuffers[strId] != nil {
|
if s.ImageBuffers[strId] != nil {
|
||||||
return strId, nil
|
return strId, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if manga.Thumbnail != nil {
|
if manga.Thumbnail != nil {
|
||||||
s.ImageBuffers[strId] = manga.Thumbnail
|
s.ImageBuffers[strId] = manga.Thumbnail
|
||||||
return strId, nil
|
return strId, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
url, err := s.Provider.GetThumbnail(strId)
|
url, err := s.Provider.GetThumbnail(strId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", false, err
|
||||||
}
|
}
|
||||||
ram, err := addFileToRam(url)
|
ram, err := addFileToRam(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", false, err
|
||||||
}
|
}
|
||||||
manga.Thumbnail = ram
|
manga.Thumbnail = ram
|
||||||
s.ImageBuffers[strId] = ram
|
s.ImageBuffers[strId] = ram
|
||||||
return strId, nil
|
return strId, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AppendImagesToBuf(html string) ([]view.Image, error) {
|
func (s *Server) AppendImagesToBuf(html string) ([]view.Image, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user