Compare commits
18 Commits
ProviderRe
...
Multiple-U
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1fa18fead | ||
|
|
20ad56b155 | ||
|
|
0904a1214e | ||
|
|
bf4b4a3a15 | ||
|
|
1bd80bc1c2 | ||
|
|
620a043286 | ||
|
|
56fc884952 | ||
|
|
58344d4def | ||
|
|
cb592c7109 | ||
|
|
f712211194 | ||
|
|
e3e0da86fc | ||
|
|
e45109fcd5 | ||
|
|
c83a10823d | ||
|
|
ce878efce3 | ||
|
|
1377fd420e | ||
|
|
ad1fcbc68a | ||
|
|
b7f2a389cd | ||
|
|
a8ba8728dc |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,4 +2,7 @@
|
||||
findings.txt
|
||||
test.txt
|
||||
h.html
|
||||
db.sqlite
|
||||
*.sqlite
|
||||
*.bak
|
||||
/bin
|
||||
*.exe
|
||||
8
Makefile
8
Makefile
@@ -2,10 +2,8 @@ run: develop
|
||||
bin/develop
|
||||
|
||||
develop:
|
||||
go build -tags Develop -o bin/develop cmd/mangaGetter/main.go
|
||||
|
||||
go build -tags Develop -o bin/develop
|
||||
release:
|
||||
go build -o bin/MangaGetter_unix cmd/mangaGetter/main.go
|
||||
|
||||
go build -o bin/MangaGetter_unix
|
||||
win-amd64:
|
||||
GOOS=windows GOARCH=amd64 go build -o bin/MangaGetter-amd64_windows.exe cmd/mangaGetter/main.go
|
||||
GOOS=windows GOARCH=amd64 go build -o bin/MangaGetter-amd64_windows.exe
|
||||
|
||||
@@ -8,10 +8,11 @@ That's, why I created this simple pre Loader, right now it's not really user-fri
|
||||
a few more features
|
||||
|
||||
# Features that might get added:
|
||||
- Manga / Chapter History
|
||||
- Searchbar
|
||||
- Better looking UI
|
||||
- Main Screen
|
||||
- Genres and Filter
|
||||
- More Providers like Asuratoon
|
||||
- Performance improvements
|
||||
|
||||
# Pretext
|
||||
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mangaGetter/internal/database"
|
||||
"mangaGetter/internal/provider"
|
||||
"mangaGetter/internal/server"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
fmt.Println(nil)
|
||||
return
|
||||
}
|
||||
|
||||
dirPath := filepath.Join(dir, "MangaGetter")
|
||||
filePath := filepath.Join(dirPath, "db.sqlite")
|
||||
|
||||
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
||||
err = os.Mkdir(dirPath, os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
f, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
db := database.NewDatabase(filePath, true)
|
||||
err = db.Open()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
s := server.New(&provider.Bato{}, &db)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
for range c {
|
||||
Close(&db)
|
||||
}
|
||||
}()
|
||||
|
||||
http.HandleFunc("/", s.HandleMenu)
|
||||
http.HandleFunc("/new/", s.HandleNewQuery)
|
||||
http.HandleFunc("/new/title/{title}/{chapter}", s.HandleNew)
|
||||
http.HandleFunc("/current/", s.HandleCurrent)
|
||||
http.HandleFunc("/img/{url}/", s.HandleImage)
|
||||
http.HandleFunc("POST /next", s.HandleNext)
|
||||
http.HandleFunc("POST /prev", s.HandlePrev)
|
||||
http.HandleFunc("POST /exit", s.HandleExit)
|
||||
http.HandleFunc("POST /delete", s.HandleDelete)
|
||||
http.HandleFunc("/favicon.ico", s.HandleFavicon)
|
||||
|
||||
go func() {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
err := open("http://localhost:8000")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Println("Server starting...")
|
||||
err = http.ListenAndServe(":8000", nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func open(url string) error {
|
||||
var cmd string
|
||||
var args []string
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = "cmd"
|
||||
args = []string{"/c", "start"}
|
||||
case "darwin":
|
||||
cmd = "open"
|
||||
default: // "linux", "freebsd", "openbsd", "netbsd"
|
||||
cmd = "xdg-open"
|
||||
}
|
||||
args = append(args, url)
|
||||
return exec.Command(cmd, args...).Start()
|
||||
}
|
||||
|
||||
func Close(db *database.Manager) {
|
||||
fmt.Println("Attempting to save and close DB")
|
||||
err := db.Save()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
9
develop.go
Normal file
9
develop.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build Develop
|
||||
|
||||
package main
|
||||
|
||||
const port = 8080
|
||||
|
||||
func getDbPath() string {
|
||||
return "db.sqlite"
|
||||
}
|
||||
9
go.mod
9
go.mod
@@ -1,4 +1,4 @@
|
||||
module mangaGetter
|
||||
module github.com/pablu23/mangaGetter
|
||||
|
||||
go 1.22
|
||||
|
||||
@@ -6,3 +6,10 @@ require (
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
golang.org/x/text v0.14.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
gorm.io/driver/sqlite v1.5.5
|
||||
gorm.io/gorm v1.25.10
|
||||
)
|
||||
|
||||
8
go.sum
8
go.sum
@@ -1,4 +1,12 @@
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
|
||||
24
internal/database/chapter.go
Normal file
24
internal/database/chapter.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package database
|
||||
|
||||
type Chapter struct {
|
||||
Id int `gorm:"primary_key;autoIncrement;"`
|
||||
ChapterId int
|
||||
Url string
|
||||
Name string
|
||||
Number string
|
||||
TimeStampUnix int64
|
||||
MangaId int
|
||||
UserId int
|
||||
}
|
||||
|
||||
func NewChapter(id int, mangaId int, userId int, url string, name string, number string, timeStampUnix int64) Chapter {
|
||||
return Chapter{
|
||||
ChapterId: id,
|
||||
Url: url,
|
||||
Name: name,
|
||||
Number: number,
|
||||
TimeStampUnix: timeStampUnix,
|
||||
MangaId: mangaId,
|
||||
UserId: userId,
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
create table if not exists Manga (
|
||||
ID integer not null primary key,
|
||||
Title text,
|
||||
TimeStampUnixEpoch int,
|
||||
Thumbnail blob
|
||||
);
|
||||
|
||||
create table if not exists Chapter (
|
||||
ID integer not null primary key,
|
||||
MangaID integer not null,
|
||||
Url text not null,
|
||||
Name text null,
|
||||
Number int not null,
|
||||
TimeStampUnixEpoch int,
|
||||
foreign key(MangaID) references Manga(ID)
|
||||
);
|
||||
@@ -1,222 +1,59 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Manga struct {
|
||||
Id int
|
||||
Title string
|
||||
TimeStampUnix int64
|
||||
Thumbnail *bytes.Buffer
|
||||
|
||||
// Not in DB
|
||||
LatestChapter *Chapter
|
||||
}
|
||||
|
||||
type Chapter struct {
|
||||
Id int
|
||||
Manga *Manga
|
||||
Url string
|
||||
Name string
|
||||
Number int
|
||||
TimeStampUnix int64
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
ConnectionString string
|
||||
db *sql.DB
|
||||
|
||||
Rw *sync.Mutex
|
||||
Mangas map[int]*Manga
|
||||
Chapters map[int]*Chapter
|
||||
|
||||
ConnectionString string
|
||||
Db *gorm.DB
|
||||
CreateIfNotExists bool
|
||||
}
|
||||
|
||||
func NewDatabase(connectionString string, createIfNotExists bool) Manager {
|
||||
return Manager{
|
||||
ConnectionString: connectionString,
|
||||
Rw: &sync.Mutex{},
|
||||
Mangas: make(map[int]*Manga),
|
||||
Chapters: make(map[int]*Chapter),
|
||||
Db: nil,
|
||||
CreateIfNotExists: createIfNotExists,
|
||||
}
|
||||
}
|
||||
|
||||
func (dbMgr *Manager) Open() error {
|
||||
db, err := sql.Open("sqlite3", dbMgr.ConnectionString)
|
||||
db, err := gorm.Open(sqlite.Open(dbMgr.ConnectionString), &gorm.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbMgr.db = db
|
||||
dbMgr.Db = db
|
||||
if dbMgr.CreateIfNotExists {
|
||||
err = dbMgr.createDatabaseIfNotExists()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = dbMgr.load()
|
||||
return err
|
||||
}
|
||||
|
||||
func (dbMgr *Manager) Close() error {
|
||||
err := dbMgr.db.Close()
|
||||
sql, err := dbMgr.Db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbMgr.Mangas = nil
|
||||
dbMgr.Chapters = nil
|
||||
dbMgr.db = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbMgr *Manager) Delete(mangaId int) error {
|
||||
db := dbMgr.db
|
||||
fmt.Println("Locking Rw in database.go:84")
|
||||
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)
|
||||
err = sql.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec("DELETE from Manga where ID = ?", mangaId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, chapter := range dbMgr.Chapters {
|
||||
if chapter.Manga.Id == mangaId {
|
||||
delete(dbMgr.Chapters, i)
|
||||
}
|
||||
}
|
||||
delete(dbMgr.Mangas, mangaId)
|
||||
dbMgr.Db = nil
|
||||
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) values(?, ?, ?, ?)", m.Id, m.Title, m.TimeStampUnix, m.Thumbnail.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err := db.Exec("INSERT INTO Manga(ID, Title, TimeStampUnixEpoch ) values(?, ?, ?)", m.Id, m.Title, m.TimeStampUnix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err := db.Exec("UPDATE Manga set Title = ?, TimeStampUnixEpoch = ? WHERE ID = ?", m.Title, m.TimeStampUnix, 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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
func (dbMgr *Manager) Delete(mangaId int) {
|
||||
dbMgr.Db.Delete(&Manga{}, mangaId)
|
||||
}
|
||||
|
||||
//go:embed createDb.sql
|
||||
var createSql string
|
||||
|
||||
func (dbMgr *Manager) createDatabaseIfNotExists() error {
|
||||
_, err := dbMgr.db.Exec(createSql)
|
||||
err := dbMgr.Db.AutoMigrate(&MangaDefinition{}, &User{}, &Manga{}, &Chapter{}, &Setting{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (dbMgr *Manager) load() error {
|
||||
db := 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 * FROM Manga")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
manga := Manga{}
|
||||
var thumbnail []byte
|
||||
if err = rows.Scan(&manga.Id, &manga.Title, &manga.TimeStampUnix, &thumbnail); err != nil {
|
||||
return err
|
||||
}
|
||||
manga.Thumbnail = bytes.NewBuffer(thumbnail)
|
||||
dbMgr.Mangas[manga.Id] = &manga
|
||||
}
|
||||
|
||||
rows, err = db.Query("SELECT * FROM Chapter")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
chapter := Chapter{}
|
||||
var mangaID int
|
||||
if err = rows.Scan(&chapter.Id, &mangaID, &chapter.Url, &chapter.Name, &chapter.Number, &chapter.TimeStampUnix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chapter.Manga = dbMgr.Mangas[mangaID]
|
||||
if dbMgr.Mangas[mangaID].LatestChapter == nil || dbMgr.Mangas[mangaID].LatestChapter.TimeStampUnix < chapter.TimeStampUnix {
|
||||
dbMgr.Mangas[mangaID].LatestChapter = &chapter
|
||||
}
|
||||
|
||||
dbMgr.Chapters[chapter.Id] = &chapter
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
65
internal/database/manga.go
Normal file
65
internal/database/manga.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package database
|
||||
|
||||
type MangaDefinition struct {
|
||||
Id int `gorm:"primary_key;AUTO_INCREMENT"`
|
||||
Title string
|
||||
Thumbnail []byte
|
||||
LastChapterNum string
|
||||
// Chapters []Chapter
|
||||
//`gorm:"foreignkey:MangaID"`
|
||||
}
|
||||
|
||||
type Manga struct {
|
||||
Id int `gorm:"primary_key;AUTO_INCREMENT"`
|
||||
MangaDefinitionId int
|
||||
Definition MangaDefinition `gorm:"foreignKey:MangaDefinitionId"`
|
||||
UserId int
|
||||
User User
|
||||
TimeStampUnix int64
|
||||
Chapters []Chapter `gorm:"foreignKey:MangaId"`
|
||||
}
|
||||
|
||||
func NewMangaDefinition(id int, title string) MangaDefinition {
|
||||
return MangaDefinition{
|
||||
Id: id,
|
||||
Title: title,
|
||||
LastChapterNum: "",
|
||||
}
|
||||
}
|
||||
|
||||
func NewManga(def MangaDefinition, user User, timeStampUnix int64) Manga {
|
||||
return Manga{
|
||||
MangaDefinitionId: def.Id,
|
||||
Definition: def,
|
||||
UserId: user.Id,
|
||||
User: user,
|
||||
TimeStampUnix: timeStampUnix,
|
||||
}
|
||||
}
|
||||
|
||||
// GetLatestChapter TODO: Cache this somehow
|
||||
func (m *Manga) GetLatestChapter() (*Chapter, bool) {
|
||||
// highest := int64(0)
|
||||
// index := 0
|
||||
// for i, chapter := range m.Chapters {
|
||||
// if chapter.MangaId == m.Manga.Id && highest < chapter.TimeStampUnix {
|
||||
// highest = chapter.TimeStampUnix
|
||||
// index = i
|
||||
// }
|
||||
// }
|
||||
|
||||
// if highest == 0 {
|
||||
return nil, false
|
||||
// }
|
||||
|
||||
// return &m.Chapters[index], true
|
||||
|
||||
//result := db.Where("manga.id = ?", m.Id).Order("TimeStampUnix desc").Take(&chapter)
|
||||
//if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
// return &chapter, true, result.Error
|
||||
//} else if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
// return &chapter, false, nil
|
||||
//} else {
|
||||
// return &chapter, true, nil
|
||||
//}
|
||||
}
|
||||
27
internal/database/setting.go
Normal file
27
internal/database/setting.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package database
|
||||
|
||||
type Setting struct {
|
||||
Name string `gorm:"PRIMARY_KEY"`
|
||||
Value string
|
||||
Default string
|
||||
}
|
||||
|
||||
func NewSetting(name string, defaultValue string) Setting {
|
||||
return Setting{
|
||||
Name: name,
|
||||
Value: defaultValue,
|
||||
Default: defaultValue,
|
||||
}
|
||||
}
|
||||
|
||||
//func initSettings(settings *DbTable[string, Setting]) {
|
||||
// addSettingIfNotExists("theme", "white", settings)
|
||||
// addSettingIfNotExists("order", "title", settings)
|
||||
//}
|
||||
//
|
||||
//func addSettingIfNotExists(name string, value string, settings *DbTable[string, Setting]) {
|
||||
// _, exists := settings.Get(name)
|
||||
// if !exists {
|
||||
// settings.Set(name, NewSetting(name, value))
|
||||
// }
|
||||
//}
|
||||
17
internal/database/user.go
Normal file
17
internal/database/user.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package database
|
||||
|
||||
type User struct {
|
||||
Id int `gorm:"primary_key;AUTO_INCREMENT"`
|
||||
DisplayName string
|
||||
LoginName string
|
||||
PwdHash []byte
|
||||
Salt []byte
|
||||
}
|
||||
|
||||
// type UserManga struct {
|
||||
// Id int `gorm:"primary_key;AUTO_INCREMENT"`
|
||||
// DisplayName string
|
||||
// Manga Manga
|
||||
// User User
|
||||
// // Chapters []Chapter `gorm:"ForeignKey:ChapterId,UserId;References:Id,UserId"`
|
||||
// }
|
||||
@@ -109,9 +109,24 @@ func (b *Bato) GetTitleIdAndChapterId(url string) (titleId int, chapterId int, e
|
||||
return t, c, err
|
||||
}
|
||||
|
||||
//func (b *Bato) GetChapterList(url string) (chapterIds []int, err error) {
|
||||
//
|
||||
//}
|
||||
func (b *Bato) GetChapterList(subUrl string) (subUrls []string, err error) {
|
||||
reg, err := regexp.Compile(`<div class="space-x-1">.*?<a href="(.*?)" .*?>.*?</a>`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
html, err := b.GetHtml(subUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subUrls = make([]string, 0)
|
||||
matches := reg.FindAllStringSubmatch(html, -1)
|
||||
for _, match := range matches {
|
||||
subUrls = append(subUrls, match[1])
|
||||
}
|
||||
return subUrls, nil
|
||||
}
|
||||
|
||||
func (b *Bato) GetThumbnail(subUrl string) (thumbnailUrl string, err error) {
|
||||
url := fmt.Sprintf("https://bato.to/title/%s", subUrl)
|
||||
|
||||
@@ -8,4 +8,5 @@ type Provider interface {
|
||||
GetTitleAndChapter(url string) (title string, chapter string, err error)
|
||||
GetTitleIdAndChapterId(url string) (titleId int, chapterId int, err error)
|
||||
GetThumbnail(mangaId string) (thumbnailUrl string, err error)
|
||||
GetChapterList(url string) (urls []string, err error)
|
||||
}
|
||||
|
||||
@@ -1,87 +1,226 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"html/template"
|
||||
"mangaGetter/internal/database"
|
||||
"mangaGetter/internal/view"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pablu23/mangaGetter/internal/database"
|
||||
"github.com/pablu23/mangaGetter/internal/view"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (s *Server) getSessionFromCookie(w http.ResponseWriter, r *http.Request) (*UserSession, error) {
|
||||
cookie, err := r.Cookie("session")
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, http.ErrNoCookie):
|
||||
// http.Error(w, "cookie not found", http.StatusBadRequest)
|
||||
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
||||
default:
|
||||
fmt.Println(err)
|
||||
http.Error(w, "server error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
session, ok := s.Sessions[cookie.Value]
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
||||
return nil, errors.New("Unknown Session")
|
||||
}
|
||||
return session, err
|
||||
}
|
||||
|
||||
func (s *Server) HandleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
s.Mutex.Lock()
|
||||
defer s.Mutex.Unlock()
|
||||
|
||||
admin := database.User{
|
||||
Id: 1,
|
||||
DisplayName: "admin",
|
||||
LoginName: "admin",
|
||||
}
|
||||
s.DbMgr.Db.Create(&admin)
|
||||
|
||||
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
s.Mutex.Lock()
|
||||
defer s.Mutex.Unlock()
|
||||
|
||||
// Login
|
||||
s.Sessions["abcd"] = &UserSession{
|
||||
User: database.User{
|
||||
Id: 1,
|
||||
DisplayName: "admin",
|
||||
LoginName: "admin",
|
||||
},
|
||||
}
|
||||
|
||||
cookie := http.Cookie{
|
||||
Name: "session",
|
||||
Value: "abcd",
|
||||
Path: "/",
|
||||
MaxAge: 3600,
|
||||
Secure: true,
|
||||
HttpOnly: false,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
}
|
||||
|
||||
http.SetCookie(w, &cookie)
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) HandleNew(w http.ResponseWriter, r *http.Request) {
|
||||
title := r.PathValue("title")
|
||||
chapter := r.PathValue("chapter")
|
||||
|
||||
url := fmt.Sprintf("/title/%s/%s", title, chapter)
|
||||
|
||||
s.Mutex.Lock()
|
||||
s.ImageBuffers = make(map[string]*bytes.Buffer)
|
||||
s.Mutex.Unlock()
|
||||
s.CurrSubUrl = url
|
||||
s.PrevSubUrl = ""
|
||||
s.NextSubUrl = ""
|
||||
s.LoadCurr()
|
||||
session, err := s.getSessionFromCookie(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go s.LoadNext()
|
||||
go s.LoadPrev()
|
||||
session.CurrSubUrl = url
|
||||
session.PrevSubUrl = ""
|
||||
session.NextSubUrl = ""
|
||||
s.LoadCurr(session)
|
||||
|
||||
go s.LoadNext(session)
|
||||
go s.LoadPrev(session)
|
||||
|
||||
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) HandleMenu(w http.ResponseWriter, _ *http.Request) {
|
||||
func (s *Server) HandleMenu(w http.ResponseWriter, r *http.Request) {
|
||||
tmpl := template.Must(view.GetViewTemplate(view.Menu))
|
||||
|
||||
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
|
||||
session, err := s.getSessionFromCookie(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var all []*database.Manga
|
||||
_ = s.DbMgr.Db.Preload("Chapters").Where("user_id = ?", session.User.Id).Find(&all)
|
||||
l := len(all)
|
||||
mangaViewModels := make([]view.MangaViewModel, l)
|
||||
counter := 0
|
||||
|
||||
for _, manga := range all {
|
||||
title := cases.Title(language.English, cases.Compact).String(strings.Replace(manga.Title, "-", " ", -1))
|
||||
n := time.Now().UnixNano()
|
||||
|
||||
thumbnail, err := s.LoadThumbnail(manga.Id)
|
||||
var tmp []database.Setting
|
||||
s.DbMgr.Db.Find(&tmp)
|
||||
settings := make(map[string]database.Setting)
|
||||
for _, m := range tmp {
|
||||
settings[m.Name] = m
|
||||
}
|
||||
|
||||
var thumbNs int64 = 0
|
||||
var titNs int64 = 0
|
||||
|
||||
//TODO: Change all this to be more performant
|
||||
for _, manga := range all {
|
||||
title := cases.Title(language.English, cases.Compact).String(strings.Replace(manga.Definition.Title, "-", " ", -1))
|
||||
|
||||
t1 := time.Now().UnixNano()
|
||||
|
||||
thumbnail, updated, err := s.LoadThumbnail(manga)
|
||||
//TODO: Add default picture instead of not showing Manga at all
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
manga.Thumbnail = s.ImageBuffers[thumbnail]
|
||||
if updated {
|
||||
s.DbMgr.Db.Save(manga)
|
||||
}
|
||||
|
||||
t2 := time.Now().UnixNano()
|
||||
|
||||
thumbNs += t2 - t1
|
||||
|
||||
t1 = time.Now().UnixNano()
|
||||
|
||||
// This is very slow
|
||||
// TODO: put this into own Method
|
||||
if manga.Definition.LastChapterNum == "" {
|
||||
err, updated := s.UpdateLatestAvailableChapter(manga)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
if updated {
|
||||
s.DbMgr.Db.Save(manga.Definition)
|
||||
}
|
||||
}
|
||||
|
||||
t2 = time.Now().UnixNano()
|
||||
|
||||
titNs += t2 - t1
|
||||
latestChapter, ok := manga.GetLatestChapter()
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
mangaViewModels[counter] = view.MangaViewModel{
|
||||
ID: manga.Id,
|
||||
Title: title,
|
||||
Number: manga.LatestChapter.Number,
|
||||
ID: manga.Definition.Id,
|
||||
Title: title,
|
||||
Number: latestChapter.Number,
|
||||
LastNumber: manga.Definition.LastChapterNum,
|
||||
// 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)"),
|
||||
Url: manga.LatestChapter.Url,
|
||||
Url: latestChapter.Url,
|
||||
ThumbnailUrl: thumbnail,
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int {
|
||||
return cmp.Compare(a.Title, b.Title)
|
||||
})
|
||||
fmt.Printf("Loading Thumbnails took %d ms\n", (thumbNs)/1000000)
|
||||
fmt.Printf("Loading latest Chapters took %d ms\n", (titNs)/1000000)
|
||||
|
||||
menuViewModel := view.MenuViewModel{
|
||||
Mangas: mangaViewModels,
|
||||
nex := time.Now().UnixNano()
|
||||
fmt.Printf("Creating Viewmodels took %d ms\n", (nex-n)/1000000)
|
||||
|
||||
n = time.Now().UnixNano()
|
||||
|
||||
order, ok := settings["order"]
|
||||
if !ok || order.Value == "title" {
|
||||
slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int {
|
||||
return cmp.Compare(a.Title, b.Title)
|
||||
})
|
||||
} else if order.Value == "chapter" {
|
||||
slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int {
|
||||
return cmp.Compare(b.Number, a.Number)
|
||||
})
|
||||
} else if order.Value == "last" {
|
||||
slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int {
|
||||
aT, err := time.Parse("15:04 (02-01-06)", a.LastTime)
|
||||
if err != nil {
|
||||
return cmp.Compare(a.Title, b.Title)
|
||||
}
|
||||
bT, err := time.Parse("15:04 (02-01-06)", b.LastTime)
|
||||
if err != nil {
|
||||
return cmp.Compare(a.Title, b.Title)
|
||||
}
|
||||
return bT.Compare(aT)
|
||||
})
|
||||
}
|
||||
|
||||
err := tmpl.Execute(w, menuViewModel)
|
||||
nex = time.Now().UnixNano()
|
||||
fmt.Printf("Sorting took %d ms\n", (nex-n)/1000000)
|
||||
|
||||
menuViewModel := view.MenuViewModel{
|
||||
Settings: settings,
|
||||
Mangas: mangaViewModels,
|
||||
}
|
||||
|
||||
err = tmpl.Execute(w, menuViewModel)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
@@ -102,80 +241,85 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = s.DbMgr.Delete(mangaId)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
s.DbMgr.Delete(mangaId)
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) HandleExit(w http.ResponseWriter, r *http.Request) {
|
||||
err := s.DbMgr.Save()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
|
||||
// session, err := s.getSessionFromCookie(w, r)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
go func() {
|
||||
// session.Mutex.Lock()
|
||||
// if session.PrevViewModel != nil {
|
||||
// for _, img := range session.PrevViewModel.Images {
|
||||
// delete(s.ImageBuffers, img.Path)
|
||||
// }
|
||||
// }
|
||||
// if session.CurrViewModel != nil {
|
||||
//
|
||||
// for _, img := range session.CurrViewModel.Images {
|
||||
// delete(s.ImageBuffers, img.Path)
|
||||
// }
|
||||
// }
|
||||
// if session.NextViewModel != nil {
|
||||
//
|
||||
// for _, img := range session.NextViewModel.Images {
|
||||
// delete(s.ImageBuffers, img.Path)
|
||||
// }
|
||||
// }
|
||||
// session.Mutex.Unlock()
|
||||
fmt.Println("Cleaned last Manga")
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Server) HandleCurrent(w http.ResponseWriter, _ *http.Request) {
|
||||
func (s *Server) HandleCurrent(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
session, err := s.getSessionFromCookie(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mangaId, chapterId, err := s.Provider.GetTitleIdAndChapterId(session.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
var manga *database.Manga
|
||||
if s.DbMgr.Mangas[mangaId] == nil {
|
||||
manga = &database.Manga{
|
||||
Id: mangaId,
|
||||
Title: title,
|
||||
TimeStampUnix: time.Now().Unix(),
|
||||
}
|
||||
s.DbMgr.Mangas[mangaId] = manga
|
||||
} else {
|
||||
manga = s.DbMgr.Mangas[mangaId]
|
||||
s.DbMgr.Mangas[mangaId].TimeStampUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
if s.DbMgr.Chapters[chapterId] == nil {
|
||||
chapterNumberStr := strings.Replace(chapter, "ch_", "", 1)
|
||||
number, err := strconv.Atoi(chapterNumberStr)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
number = 0
|
||||
}
|
||||
|
||||
s.DbMgr.Chapters[chapterId] = &database.Chapter{
|
||||
Id: chapterId,
|
||||
Manga: manga,
|
||||
Url: s.CurrSubUrl,
|
||||
Name: chapter,
|
||||
Number: number,
|
||||
TimeStampUnix: time.Now().Unix(),
|
||||
}
|
||||
} else {
|
||||
s.DbMgr.Chapters[chapterId].TimeStampUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
s.DbMgr.Mangas[mangaId].LatestChapter = s.DbMgr.Chapters[chapterId]
|
||||
}
|
||||
}
|
||||
|
||||
err = tmpl.Execute(w, s.CurrViewModel)
|
||||
title, chapterName, err := s.Provider.GetTitleAndChapter(session.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
var mangaDef database.MangaDefinition
|
||||
result := s.DbMgr.Db.First(&mangaDef, mangaId)
|
||||
if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
mangaDef = database.NewMangaDefinition(mangaId, title)
|
||||
}
|
||||
|
||||
var manga database.Manga
|
||||
result = s.DbMgr.Db.Where("user_id = ?", session.User.Id).First(&manga, "manga_definition_id = ?", mangaId)
|
||||
if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
manga = database.NewManga(mangaDef, session.User, time.Now().Unix())
|
||||
}
|
||||
|
||||
var chapter database.Chapter
|
||||
result = s.DbMgr.Db.Where("user_id = ?", session.User.Id).First(&chapter, "chapter_id = ?", chapterId)
|
||||
if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
chapterNumberStr := strings.Replace(chapterName, "ch_", "", 1)
|
||||
chapter = database.NewChapter(chapterId, mangaId, session.User.Id, session.CurrSubUrl, chapterName, chapterNumberStr, time.Now().Unix())
|
||||
} else {
|
||||
chapter.TimeStampUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
s.DbMgr.Db.Save(&mangaDef)
|
||||
s.DbMgr.Db.Save(&manga)
|
||||
s.DbMgr.Db.Save(&chapter)
|
||||
|
||||
err = tmpl.Execute(w, session.CurrViewModel)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
@@ -183,8 +327,8 @@ func (s *Server) HandleCurrent(w http.ResponseWriter, _ *http.Request) {
|
||||
|
||||
func (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) {
|
||||
u := r.PathValue("url")
|
||||
s.Mutex.Lock()
|
||||
defer s.Mutex.Unlock()
|
||||
s.Mutex.RLock()
|
||||
defer s.Mutex.RUnlock()
|
||||
buf := s.ImageBuffers[u]
|
||||
if buf == nil {
|
||||
fmt.Printf("url: %s is nil\n", u)
|
||||
@@ -193,7 +337,7 @@ func (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "image/webp")
|
||||
_, err := w.Write(buf.Bytes())
|
||||
_, err := w.Write(buf)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
@@ -202,7 +346,7 @@ func (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) {
|
||||
//go:embed favicon.ico
|
||||
var ico []byte
|
||||
|
||||
func (s *Server) HandleFavicon(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) HandleFavicon(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "image/webp")
|
||||
_, err := w.Write(ico)
|
||||
if err != nil {
|
||||
@@ -213,7 +357,12 @@ func (s *Server) HandleFavicon(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) HandleNext(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Received Next")
|
||||
|
||||
if s.PrevViewModel != nil {
|
||||
session, err := s.getSessionFromCookie(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if session.PrevViewModel != nil {
|
||||
go func(viewModel view.ImageViewModel, s *Server) {
|
||||
s.Mutex.Lock()
|
||||
for _, img := range viewModel.Images {
|
||||
@@ -221,30 +370,31 @@ func (s *Server) HandleNext(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
s.Mutex.Unlock()
|
||||
fmt.Println("Cleaned out of scope Last")
|
||||
}(*s.PrevViewModel, s)
|
||||
}(*session.PrevViewModel, s)
|
||||
}
|
||||
|
||||
if s.NextViewModel == nil || s.NextSubUrl == "" {
|
||||
if session.NextViewModel == nil || session.NextSubUrl == "" {
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
err := s.DbMgr.Save()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
s.PrevViewModel = s.CurrViewModel
|
||||
s.CurrViewModel = s.NextViewModel
|
||||
s.PrevSubUrl = s.CurrSubUrl
|
||||
s.CurrSubUrl = s.NextSubUrl
|
||||
session.PrevViewModel = session.CurrViewModel
|
||||
session.CurrViewModel = session.NextViewModel
|
||||
session.PrevSubUrl = session.CurrSubUrl
|
||||
session.CurrSubUrl = session.NextSubUrl
|
||||
|
||||
go s.LoadNext()
|
||||
go s.LoadNext(session)
|
||||
|
||||
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
func (s *Server) HandlePrev(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Received Prev")
|
||||
if s.NextViewModel != nil {
|
||||
|
||||
session, err := s.getSessionFromCookie(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if session.NextViewModel != nil {
|
||||
go func(viewModel view.ImageViewModel, s *Server) {
|
||||
s.Mutex.Lock()
|
||||
for _, img := range viewModel.Images {
|
||||
@@ -252,43 +402,74 @@ func (s *Server) HandlePrev(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
s.Mutex.Unlock()
|
||||
fmt.Println("Cleaned out of scope Last")
|
||||
}(*s.NextViewModel, s)
|
||||
}(*session.NextViewModel, s)
|
||||
}
|
||||
|
||||
if s.PrevViewModel == nil || s.PrevSubUrl == "" {
|
||||
if session.PrevViewModel == nil || session.PrevSubUrl == "" {
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
err := s.DbMgr.Save()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
s.NextViewModel = s.CurrViewModel
|
||||
s.CurrViewModel = s.PrevViewModel
|
||||
s.NextSubUrl = s.CurrSubUrl
|
||||
s.CurrSubUrl = s.PrevSubUrl
|
||||
session.NextViewModel = session.CurrViewModel
|
||||
session.CurrViewModel = session.PrevViewModel
|
||||
session.NextSubUrl = session.CurrSubUrl
|
||||
session.CurrSubUrl = session.PrevSubUrl
|
||||
|
||||
go s.LoadPrev()
|
||||
go s.LoadPrev(session)
|
||||
|
||||
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) HandleSettingSet(w http.ResponseWriter, r *http.Request) {
|
||||
settingName := r.PathValue("setting")
|
||||
settingValue := r.PathValue("value")
|
||||
|
||||
var setting database.Setting
|
||||
res := s.DbMgr.Db.First(&setting, "name = ?", settingName)
|
||||
|
||||
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
set := database.NewSetting(settingName, settingValue)
|
||||
s.DbMgr.Db.Save(&set)
|
||||
} else {
|
||||
s.DbMgr.Db.Model(&setting).Update("value", settingValue)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) HandleSetting(w http.ResponseWriter, r *http.Request) {
|
||||
settingName := r.PostFormValue("setting")
|
||||
settingValue := r.PostFormValue(settingName)
|
||||
|
||||
var setting database.Setting
|
||||
res := s.DbMgr.Db.First(&setting, "name = ?", settingName)
|
||||
|
||||
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
set := database.NewSetting(settingName, settingValue)
|
||||
s.DbMgr.Db.Save(&set)
|
||||
} else {
|
||||
s.DbMgr.Db.Model(&setting).Update("value", settingValue)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) HandleNewQuery(w http.ResponseWriter, r *http.Request) {
|
||||
sub := r.PostFormValue("subUrl")
|
||||
|
||||
url := fmt.Sprintf("/title/%s", sub)
|
||||
|
||||
s.Mutex.Lock()
|
||||
s.ImageBuffers = make(map[string]*bytes.Buffer)
|
||||
s.Mutex.Unlock()
|
||||
s.CurrSubUrl = url
|
||||
s.PrevSubUrl = ""
|
||||
s.NextSubUrl = ""
|
||||
s.LoadCurr()
|
||||
session, err := s.getSessionFromCookie(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
session.CurrSubUrl = url
|
||||
session.PrevSubUrl = ""
|
||||
session.NextSubUrl = ""
|
||||
s.LoadCurr(session)
|
||||
|
||||
go s.LoadNext()
|
||||
go s.LoadPrev()
|
||||
go s.LoadNext(session)
|
||||
go s.LoadPrev(session)
|
||||
|
||||
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
@@ -5,77 +5,130 @@ import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"mangaGetter/internal/database"
|
||||
"mangaGetter/internal/provider"
|
||||
"mangaGetter/internal/view"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pablu23/mangaGetter/internal/database"
|
||||
"github.com/pablu23/mangaGetter/internal/provider"
|
||||
"github.com/pablu23/mangaGetter/internal/view"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
ImageBuffers map[string][]byte
|
||||
Provider provider.Provider
|
||||
DbMgr *database.Manager
|
||||
|
||||
Mutex *sync.RWMutex
|
||||
|
||||
Sessions map[string]*UserSession
|
||||
}
|
||||
|
||||
type UserSession struct {
|
||||
User database.User
|
||||
|
||||
// Mutex *sync.Mutex
|
||||
|
||||
PrevSubUrl string
|
||||
CurrSubUrl string
|
||||
NextSubUrl string
|
||||
|
||||
PrevViewModel *view.ImageViewModel
|
||||
CurrViewModel *view.ImageViewModel
|
||||
NextViewModel *view.ImageViewModel
|
||||
|
||||
ImageBuffers map[string]*bytes.Buffer
|
||||
Mutex *sync.Mutex
|
||||
|
||||
NextSubUrl string
|
||||
CurrSubUrl string
|
||||
PrevSubUrl string
|
||||
|
||||
Provider provider.Provider
|
||||
|
||||
IsFirst bool
|
||||
IsLast bool
|
||||
|
||||
DbMgr *database.Manager
|
||||
}
|
||||
|
||||
func New(provider provider.Provider, db *database.Manager) *Server {
|
||||
s := Server{
|
||||
ImageBuffers: make(map[string]*bytes.Buffer),
|
||||
ImageBuffers: make(map[string][]byte),
|
||||
Sessions: make(map[string]*UserSession),
|
||||
Provider: provider,
|
||||
DbMgr: db,
|
||||
Mutex: &sync.Mutex{},
|
||||
Mutex: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *Server) LoadNext() {
|
||||
c, err := s.Provider.GetHtml(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
s.NextSubUrl = ""
|
||||
s.NextViewModel = nil
|
||||
return
|
||||
}
|
||||
func (s *Server) Start(port int) error {
|
||||
http.HandleFunc("/register", s.HandleRegister)
|
||||
http.HandleFunc("/login", s.HandleLogin)
|
||||
http.HandleFunc("/", s.HandleMenu)
|
||||
http.HandleFunc("/new/", s.HandleNewQuery)
|
||||
http.HandleFunc("/new/title/{title}/{chapter}", s.HandleNew)
|
||||
http.HandleFunc("/current/", s.HandleCurrent)
|
||||
http.HandleFunc("/img/{url}/", s.HandleImage)
|
||||
http.HandleFunc("POST /next", s.HandleNext)
|
||||
http.HandleFunc("POST /prev", s.HandlePrev)
|
||||
http.HandleFunc("POST /exit", s.HandleExit)
|
||||
http.HandleFunc("POST /delete", s.HandleDelete)
|
||||
http.HandleFunc("/favicon.ico", s.HandleFavicon)
|
||||
http.HandleFunc("POST /setting/", s.HandleSetting)
|
||||
http.HandleFunc("GET /setting/set/{setting}/{value}", s.HandleSettingSet)
|
||||
|
||||
next, err := s.Provider.GetNext(c)
|
||||
// Update Latest Chapters every 5 Minutes
|
||||
// go func(s *Server) {
|
||||
// time.AfterFunc(time.Second*10, func() {
|
||||
// var all []*database.Manga
|
||||
// s.DbMgr.Db.Find(&all)
|
||||
// for _, m := range all {
|
||||
// err, updated := s.UpdateLatestAvailableChapter(m)
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// if updated {
|
||||
// s.DbMgr.Db.Save(m)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// for {
|
||||
// select {
|
||||
// case <-time.After(time.Minute * 5):
|
||||
// var all []*database.Manga
|
||||
// s.DbMgr.Db.Find(&all)
|
||||
// for _, m := range all {
|
||||
// err, updated := s.UpdateLatestAvailableChapter(m)
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// if updated {
|
||||
// s.DbMgr.Db.Save(m)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }(s)
|
||||
//
|
||||
fmt.Println("Server starting...")
|
||||
err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) LoadNext(session *UserSession) {
|
||||
next, err := s.Provider.GetHtml(session.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
s.NextSubUrl = ""
|
||||
s.NextViewModel = nil
|
||||
session.NextSubUrl = ""
|
||||
session.NextViewModel = nil
|
||||
return
|
||||
}
|
||||
|
||||
html, err := s.Provider.GetHtml(next)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
s.NextSubUrl = ""
|
||||
s.NextViewModel = nil
|
||||
session.NextSubUrl = ""
|
||||
session.NextViewModel = nil
|
||||
return
|
||||
}
|
||||
|
||||
imagesNext, err := s.AppendImagesToBuf(html)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
s.NextSubUrl = ""
|
||||
s.NextViewModel = nil
|
||||
session.NextSubUrl = ""
|
||||
session.NextViewModel = nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,39 +140,39 @@ func (s *Server) LoadNext() {
|
||||
|
||||
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
||||
|
||||
s.NextViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
||||
s.NextSubUrl = next
|
||||
session.NextViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
||||
session.NextSubUrl = next
|
||||
fmt.Println("Loaded next")
|
||||
}
|
||||
|
||||
func (s *Server) LoadPrev() {
|
||||
c, err := s.Provider.GetHtml(s.CurrSubUrl)
|
||||
func (s *Server) LoadPrev(session *UserSession) {
|
||||
c, err := s.Provider.GetHtml(session.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
s.PrevSubUrl = ""
|
||||
s.PrevViewModel = nil
|
||||
session.PrevSubUrl = ""
|
||||
session.PrevViewModel = nil
|
||||
return
|
||||
}
|
||||
prev, err := s.Provider.GetPrev(c)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
s.PrevSubUrl = ""
|
||||
s.PrevViewModel = nil
|
||||
session.PrevSubUrl = ""
|
||||
session.PrevViewModel = nil
|
||||
return
|
||||
}
|
||||
html, err := s.Provider.GetHtml(prev)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
s.PrevSubUrl = ""
|
||||
s.PrevViewModel = nil
|
||||
session.PrevSubUrl = ""
|
||||
session.PrevViewModel = nil
|
||||
return
|
||||
}
|
||||
|
||||
imagesNext, err := s.AppendImagesToBuf(html)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
s.PrevSubUrl = ""
|
||||
s.PrevViewModel = nil
|
||||
session.PrevSubUrl = ""
|
||||
session.PrevViewModel = nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -131,21 +184,21 @@ func (s *Server) LoadPrev() {
|
||||
|
||||
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
||||
|
||||
s.PrevViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
||||
session.PrevViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
||||
|
||||
s.PrevSubUrl = prev
|
||||
session.PrevSubUrl = prev
|
||||
fmt.Println("Loaded prev")
|
||||
}
|
||||
|
||||
func (s *Server) LoadCurr() {
|
||||
html, err := s.Provider.GetHtml(s.CurrSubUrl)
|
||||
func (s *Server) LoadCurr(session *UserSession) {
|
||||
html, err := s.Provider.GetHtml(session.CurrSubUrl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
imagesCurr, err := s.AppendImagesToBuf(html)
|
||||
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(session.CurrSubUrl)
|
||||
if err != nil {
|
||||
title = "Unknown"
|
||||
chapter = "ch_?"
|
||||
@@ -153,29 +206,59 @@ func (s *Server) LoadCurr() {
|
||||
|
||||
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
||||
|
||||
s.CurrViewModel = &view.ImageViewModel{Images: imagesCurr, Title: full}
|
||||
session.CurrViewModel = &view.ImageViewModel{Images: imagesCurr, Title: full}
|
||||
fmt.Println("Loaded current")
|
||||
}
|
||||
|
||||
func (s *Server) LoadThumbnail(mangaId int) (path string, err error) {
|
||||
strId := strconv.Itoa(mangaId)
|
||||
func (s *Server) UpdateLatestAvailableChapter(manga *database.Manga) (error, bool) {
|
||||
fmt.Printf("Updating Manga: %s\n", manga.Definition.Title)
|
||||
|
||||
l, err := s.Provider.GetChapterList("/title/" + strconv.Itoa(manga.Definition.Id))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
le := len(l)
|
||||
_, c, err := s.Provider.GetTitleAndChapter(l[le-1])
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
chapterNumberStr := strings.Replace(c, "ch_", "", 1)
|
||||
|
||||
if manga.Definition.LastChapterNum == chapterNumberStr {
|
||||
return nil, false
|
||||
} else {
|
||||
manga.Definition.LastChapterNum = chapterNumberStr
|
||||
return nil, true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) LoadThumbnail(manga *database.Manga) (path string, updated bool, err error) {
|
||||
strId := strconv.Itoa(manga.Definition.Id)
|
||||
|
||||
s.Mutex.Lock()
|
||||
defer s.Mutex.Unlock()
|
||||
if s.ImageBuffers[strId] != nil {
|
||||
return strId, nil
|
||||
return strId, false, nil
|
||||
}
|
||||
|
||||
url, err := s.Provider.GetThumbnail(strconv.Itoa(mangaId))
|
||||
if manga.Definition.Thumbnail != nil {
|
||||
s.ImageBuffers[strId] = manga.Definition.Thumbnail
|
||||
return strId, false, nil
|
||||
}
|
||||
|
||||
url, err := s.Provider.GetThumbnail(strId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", false, err
|
||||
}
|
||||
ram, err := addFileToRam(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", false, err
|
||||
}
|
||||
manga.Definition.Thumbnail = ram
|
||||
s.ImageBuffers[strId] = ram
|
||||
return strId, nil
|
||||
return strId, true, nil
|
||||
}
|
||||
|
||||
func (s *Server) AppendImagesToBuf(html string) ([]view.Image, error) {
|
||||
@@ -207,7 +290,7 @@ func (s *Server) AppendImagesToBuf(html string) ([]view.Image, error) {
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func addFileToRam(url string) (*bytes.Buffer, error) {
|
||||
func addFileToRam(url string) ([]byte, error) {
|
||||
// Get the data
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
@@ -224,5 +307,5 @@ func addFileToRam(url string) (*bytes.Buffer, error) {
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(buf, resp.Body)
|
||||
return buf, err
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
31
internal/utils/concurrent_map.go
Normal file
31
internal/utils/concurrent_map.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package utils
|
||||
|
||||
import "sync"
|
||||
|
||||
type ConcurrentMap[K comparable, Value any] struct {
|
||||
dirty map[K]Value
|
||||
count int
|
||||
mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
func NewConcurrentMap[K comparable, V any]() ConcurrentMap[K, V] {
|
||||
return ConcurrentMap[K, V]{
|
||||
dirty: make(map[K]V),
|
||||
count: 0,
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ConcurrentMap[K, Value]) Get(key K) (Value, bool) {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
val, ok := c.dirty[key]
|
||||
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (c *ConcurrentMap[K, Value]) Set(key K, val Value) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.dirty[key] = val
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<!--suppress CssUnusedSymbol -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@@ -12,11 +13,16 @@
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
.dark {
|
||||
background-color: #171717;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.white {
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.button-36 {
|
||||
background-image: linear-gradient(92.88deg, #455EB5 9.16%, #5643CC 43.89%, #673FD7 64.72%);
|
||||
border-radius: 8px;
|
||||
@@ -101,17 +107,22 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 10em;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
function myFunction() {
|
||||
var element = document.body;
|
||||
element.classList.toggle("dark-mode");
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="{{(index .Settings "theme").Value}}">
|
||||
<form method="post" action="/new/">
|
||||
<label>
|
||||
New Sub Url
|
||||
@@ -119,14 +130,22 @@
|
||||
</label>
|
||||
<input type="submit" value="Open" class="button-36">
|
||||
</form>
|
||||
<button onclick="myFunction()">Toggle dark mode</button>
|
||||
|
||||
<form method="post" action="/setting/">
|
||||
<label for="theme">Theme</label>
|
||||
<select onchange="this.form.submit()" id="theme" name="theme">
|
||||
<option {{if eq (index .Settings "theme").Value "white"}} selected {{end}} value="white">White</option>
|
||||
<option {{if eq (index .Settings "theme").Value "dark"}} selected {{end}} value="dark">Dark</option>
|
||||
</select>
|
||||
<input type="hidden" name="setting" value="theme">
|
||||
</form>
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Thumbnail</th>
|
||||
<th class="table-left">Title</th>
|
||||
<th>Current Chapter</th>
|
||||
<th>Last Accessed</th>
|
||||
<th class="table-left"><a href="setting/set/order/title">Title</a></th>
|
||||
<th><a href="setting/set/order/chapter">Current Chapter</a></th>
|
||||
<th><a href="setting/set/order/last">Last Accessed</a></th>
|
||||
<th>Link</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
@@ -138,10 +157,10 @@
|
||||
</a>
|
||||
</td>
|
||||
<td class="table-left">{{.Title}}</td>
|
||||
<td>{{.Number}}</td>
|
||||
<td>{{.Number}} / {{.LastNumber}}</td>
|
||||
<td>{{.LastTime}}</td>
|
||||
<td>
|
||||
<a href="/new/{{.Url}}}">
|
||||
<a href="/new/{{.Url}}">
|
||||
<button class="button-36">
|
||||
To chapter
|
||||
</button>
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.scroll-container img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* I have no clue what css is, jesus christ ...
|
||||
*/
|
||||
@@ -45,6 +51,18 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.fixed-button {
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
right: 15px;
|
||||
margin-bottom: 40px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.text{
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button-36 {
|
||||
background-image: linear-gradient(92.88deg, #455EB5 9.16%, #5643CC 43.89%, #673FD7 64.72%);
|
||||
border-radius: 8px;
|
||||
@@ -79,13 +97,17 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="center">
|
||||
<h1 class="center text">{{.Title}}</h1>
|
||||
<div class="center" id="top">
|
||||
<form method="post" action="/prev">
|
||||
<input type="submit" name="Prev" value="Prev" class="button-36">
|
||||
<input type="submit" name="Exit" value="Exit" class="button-36" formaction="/exit">
|
||||
<input type="submit" name="Next" value="Next" class="button-36" formaction="/next">
|
||||
</form>
|
||||
</div>
|
||||
<button class="fixed-button">
|
||||
<a href="#top">TOP</a>
|
||||
</button>
|
||||
<div class="scroll-container">
|
||||
{{range .Images}}
|
||||
<img src="/img/{{.Path}}" alt="img_{{.Index}}"/>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package view
|
||||
|
||||
import "github.com/pablu23/mangaGetter/internal/database"
|
||||
|
||||
type Image struct {
|
||||
Path string
|
||||
Index int
|
||||
@@ -13,12 +15,14 @@ type ImageViewModel struct {
|
||||
type MangaViewModel struct {
|
||||
ID int
|
||||
Title string
|
||||
Number int
|
||||
Number string
|
||||
LastNumber string
|
||||
LastTime string
|
||||
Url string
|
||||
ThumbnailUrl string
|
||||
}
|
||||
|
||||
type MenuViewModel struct {
|
||||
Mangas []MangaViewModel
|
||||
Settings map[string]database.Setting
|
||||
Mangas []MangaViewModel
|
||||
}
|
||||
|
||||
74
main.go
Normal file
74
main.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pablu23/mangaGetter/internal/database"
|
||||
"github.com/pablu23/mangaGetter/internal/provider"
|
||||
"github.com/pablu23/mangaGetter/internal/server"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
filePath := getDbPath()
|
||||
|
||||
db := database.NewDatabase(filePath, true)
|
||||
err := db.Open()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
s := server.New(&provider.Bato{}, &db)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
for range c {
|
||||
Close(&db)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
err := open(fmt.Sprintf("http://localhost:%d", port))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = s.Start(port)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func open(url string) error {
|
||||
var cmd string
|
||||
var args []string
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = "cmd"
|
||||
args = []string{"/c", "start"}
|
||||
case "darwin":
|
||||
cmd = "open"
|
||||
default: // "linux", "freebsd", "openbsd", "netbsd"
|
||||
cmd = "xdg-open"
|
||||
}
|
||||
args = append(args, url)
|
||||
return exec.Command(cmd, args...).Start()
|
||||
}
|
||||
|
||||
func Close(db *database.Manager) {
|
||||
fmt.Println("Attempting to save and close DB")
|
||||
err := db.Close()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
BIN
mangaGetter
Executable file
BIN
mangaGetter
Executable file
Binary file not shown.
40
release.go
Normal file
40
release.go
Normal file
@@ -0,0 +1,40 @@
|
||||
//go:build !Develop
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const port = 8000
|
||||
|
||||
func getDbPath() string {
|
||||
dir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dirPath := filepath.Join(dir, "MangaGetter")
|
||||
filePath := filepath.Join(dirPath, "db.sqlite")
|
||||
|
||||
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
||||
err = os.Mkdir(dirPath, os.ModePerm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
f, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return filePath
|
||||
}
|
||||
Reference in New Issue
Block a user