Compare commits
1 Commits
master
...
ProviderRe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18d9ecf4ad |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -2,11 +2,4 @@
|
|||||||
findings.txt
|
findings.txt
|
||||||
test.txt
|
test.txt
|
||||||
h.html
|
h.html
|
||||||
*.sqlite
|
db.sqlite
|
||||||
*.bak
|
|
||||||
/bin
|
|
||||||
*.exe
|
|
||||||
*secret*
|
|
||||||
mangaGetter
|
|
||||||
*.crt
|
|
||||||
*.key
|
|
||||||
|
|||||||
11
Makefile
11
Makefile
@@ -1,8 +1,11 @@
|
|||||||
run: develop
|
run: develop
|
||||||
bin/develop --secret test --server --port 8181 --database db.sqlite --debug --pretty
|
bin/develop
|
||||||
|
|
||||||
develop:
|
develop:
|
||||||
go build -tags Develop -o bin/develop
|
go build -tags Develop -o bin/develop cmd/mangaGetter/main.go
|
||||||
|
|
||||||
release:
|
release:
|
||||||
go build -o bin/MangaGetter_unix
|
go build -o bin/MangaGetter_unix cmd/mangaGetter/main.go
|
||||||
|
|
||||||
win-amd64:
|
win-amd64:
|
||||||
GOOS=windows GOARCH=amd64 go build -o bin/MangaGetter-amd64_windows.exe
|
GOOS=windows GOARCH=amd64 go build -o bin/MangaGetter-amd64_windows.exe cmd/mangaGetter/main.go
|
||||||
@@ -8,11 +8,10 @@ That's, why I created this simple pre Loader, right now it's not really user-fri
|
|||||||
a few more features
|
a few more features
|
||||||
|
|
||||||
# Features that might get added:
|
# Features that might get added:
|
||||||
|
- Manga / Chapter History
|
||||||
- Searchbar
|
- Searchbar
|
||||||
- Better looking UI
|
- Better looking UI
|
||||||
- Genres and Filter
|
- Main Screen
|
||||||
- More Providers like Asuratoon
|
|
||||||
- Performance improvements
|
|
||||||
|
|
||||||
# Pretext
|
# Pretext
|
||||||
|
|
||||||
|
|||||||
121
cmd/mangaGetter/main.go
Normal file
121
cmd/mangaGetter/main.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mangaGetter/internal/database"
|
||||||
|
"mangaGetter/internal/server"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"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("db.sqlite", true)
|
||||||
|
err := db.Open()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := server.New(&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("POST /new/", s.HandleNewQuery)
|
||||||
|
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)
|
||||||
|
}
|
||||||
15
develop.go
15
develop.go
@@ -1,15 +0,0 @@
|
|||||||
//go:build Develop
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
func getSecretPath() (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSecret() (string, error) {
|
|
||||||
return "test", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDbPath() string {
|
|
||||||
return "db.sqlite"
|
|
||||||
}
|
|
||||||
18
go.mod
18
go.mod
@@ -1,4 +1,4 @@
|
|||||||
module github.com/pablu23/mangaGetter
|
module mangaGetter
|
||||||
|
|
||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
@@ -7,18 +7,4 @@ require (
|
|||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/rs/zerolog v1.33.0 // indirect
|
|
||||||
go.uber.org/multierr v1.10.0 // indirect
|
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
|
||||||
gorm.io/driver/sqlite v1.5.5
|
|
||||||
gorm.io/gorm v1.25.10
|
|
||||||
)
|
|
||||||
|
|||||||
33
go.sum
33
go.sum
@@ -1,35 +1,6 @@
|
|||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
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-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
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=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
|
||||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
|
||||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
|
||||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
|
||||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
|
||||||
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=
|
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
type Chapter struct {
|
type Chapter struct {
|
||||||
Id int `gorm:"primary_key;AUTO_INCREMENT"`
|
Id int
|
||||||
|
Manga *Manga
|
||||||
|
InternalIdentifier string
|
||||||
Url string
|
Url string
|
||||||
Name string
|
Name string
|
||||||
Number string
|
Number int
|
||||||
TimeStampUnix int64
|
TimeStampUnix int64
|
||||||
MangaId int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewChapter(id int, mangaId int, url string, name string, number string, timeStampUnix int64) Chapter {
|
|
||||||
return Chapter{
|
|
||||||
Id: id,
|
|
||||||
Url: url,
|
|
||||||
Name: name,
|
|
||||||
Number: number,
|
|
||||||
TimeStampUnix: timeStampUnix,
|
|
||||||
MangaId: mangaId,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
27
internal/database/createDb.sql
Normal file
27
internal/database/createDb.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
drop table if exists Chapter;
|
||||||
|
drop table if exists Manga;
|
||||||
|
|
||||||
|
create table if not exists Manga
|
||||||
|
(
|
||||||
|
ID integer not null primary key autoincrement,
|
||||||
|
|
||||||
|
Provider integer not null,
|
||||||
|
|
||||||
|
Rating integer,
|
||||||
|
Title text,
|
||||||
|
TimeStampUnixEpoch integer,
|
||||||
|
Thumbnail blob
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists Chapter
|
||||||
|
(
|
||||||
|
ID integer not null primary key autoincrement,
|
||||||
|
MangaID integer not null,
|
||||||
|
|
||||||
|
InternalIdentifier text not null,
|
||||||
|
Url text not null,
|
||||||
|
Name text null,
|
||||||
|
Number integer not null,
|
||||||
|
TimeStampUnixEpoch integer,
|
||||||
|
foreign key (MangaID) references Manga (ID)
|
||||||
|
);
|
||||||
@@ -1,75 +1,206 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"mangaGetter/internal/provider"
|
||||||
|
"sync"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
ConnectionString string
|
ConnectionString string
|
||||||
Db *gorm.DB
|
db *sql.DB
|
||||||
|
|
||||||
|
Rw *sync.Mutex
|
||||||
|
Mangas map[int]*Manga
|
||||||
|
Chapters map[int]*Chapter
|
||||||
|
|
||||||
CreateIfNotExists bool
|
CreateIfNotExists bool
|
||||||
ActivateGormLogger bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDatabase(connectionString string, createIfNotExists bool, activateGormLogger bool) Manager {
|
func NewDatabase(connectionString string, createIfNotExists bool) Manager {
|
||||||
return Manager{
|
return Manager{
|
||||||
ConnectionString: connectionString,
|
ConnectionString: connectionString,
|
||||||
Db: nil,
|
Rw: &sync.Mutex{},
|
||||||
|
Mangas: make(map[int]*Manga),
|
||||||
|
Chapters: make(map[int]*Chapter),
|
||||||
CreateIfNotExists: createIfNotExists,
|
CreateIfNotExists: createIfNotExists,
|
||||||
ActivateGormLogger: activateGormLogger,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbMgr *Manager) Open() error {
|
func (dbMgr *Manager) Open() error {
|
||||||
var db *gorm.DB
|
db, err := sql.Open("sqlite3", dbMgr.ConnectionString)
|
||||||
var err error
|
|
||||||
if dbMgr.ActivateGormLogger {
|
|
||||||
db, err = gorm.Open(sqlite.Open(dbMgr.ConnectionString), &gorm.Config{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
dbMgr.db = db
|
||||||
db, err = gorm.Open(sqlite.Open(dbMgr.ConnectionString), &gorm.Config{
|
|
||||||
Logger: logger.Default.LogMode(logger.Silent),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dbMgr.Db = db
|
|
||||||
if dbMgr.CreateIfNotExists {
|
if dbMgr.CreateIfNotExists {
|
||||||
err = dbMgr.createDatabaseIfNotExists()
|
err = dbMgr.createDatabaseIfNotExists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
err = dbMgr.load()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbMgr *Manager) Close() error {
|
func (dbMgr *Manager) Close() error {
|
||||||
sql, err := dbMgr.Db.DB()
|
err := dbMgr.db.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = sql.Close()
|
|
||||||
if err != nil {
|
dbMgr.Mangas = nil
|
||||||
return err
|
dbMgr.Chapters = nil
|
||||||
}
|
dbMgr.db = nil
|
||||||
dbMgr.Db = nil
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbMgr *Manager) Delete(mangaId int) {
|
func (dbMgr *Manager) Delete(mangaId int) error {
|
||||||
dbMgr.Db.Delete(&Manga{}, mangaId)
|
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()
|
||||||
|
}()
|
||||||
|
|
||||||
func (dbMgr *Manager) createDatabaseIfNotExists() error {
|
_, err := db.Exec("DELETE from Chapter where MangaID = ?", mangaId)
|
||||||
err := dbMgr.Db.AutoMigrate(&Manga{}, &Chapter{}, &Setting{})
|
if err != nil {
|
||||||
return err
|
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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed createDb.sql
|
||||||
|
var createSql string
|
||||||
|
|
||||||
|
func (dbMgr *Manager) createDatabaseIfNotExists() error {
|
||||||
|
_, err := dbMgr.db.Exec(createSql)
|
||||||
|
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 ID, Provider, Title, TimeStampUnixEpoch, Thumbnail, Rating FROM Manga")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
manga := Manga{}
|
||||||
|
var thumbnail []byte
|
||||||
|
var providerId int
|
||||||
|
if err = rows.Scan(&manga.Id, &providerId, &manga.Title, &manga.TimeStampUnix, &thumbnail, &manga.Rating); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
manga.Thumbnail = bytes.NewBuffer(thumbnail)
|
||||||
|
manga.Provider = provider.GetProviderByType(provider.ProviderType(providerId))
|
||||||
|
dbMgr.Mangas[manga.Id] = &manga
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err = db.Query("SELECT ID, MangaID, Url, Name, Number, TimeStampUnixEpoch, InternalIdentifier 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, &chapter.InternalIdentifier); 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,49 +1,18 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"mangaGetter/internal/provider"
|
||||||
|
)
|
||||||
|
|
||||||
type Manga struct {
|
type Manga struct {
|
||||||
Id int `gorm:"primary_key;AUTO_INCREMENT"`
|
Id int
|
||||||
|
Provider provider.Provider
|
||||||
|
Rating int
|
||||||
Title string
|
Title string
|
||||||
TimeStampUnix int64
|
TimeStampUnix int64
|
||||||
Thumbnail []byte
|
Thumbnail *bytes.Buffer
|
||||||
LastChapterNum string
|
|
||||||
Chapters []Chapter
|
|
||||||
Enabled bool
|
|
||||||
//`gorm:"foreignkey:MangaID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManga(id int, title string, timeStampUnix int64) Manga {
|
// Not in DB
|
||||||
return Manga{
|
LatestChapter *Chapter
|
||||||
Id: id,
|
|
||||||
Title: title,
|
|
||||||
TimeStampUnix: timeStampUnix,
|
|
||||||
LastChapterNum: "",
|
|
||||||
Enabled: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.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
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
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))
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
100
internal/provider/asuratoon.go
Normal file
100
internal/provider/asuratoon.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Asura struct{}
|
||||||
|
|
||||||
|
func (a Asura) GetImageList(html string) (imageUrls []string, err error) {
|
||||||
|
reg, err := regexp.Compile(`<img decoding="async" class="ts-main-image " src="(.*?)"`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := reg.FindAllStringSubmatch(html, -1)
|
||||||
|
l := len(m)
|
||||||
|
result := make([]string, l)
|
||||||
|
for i, match := range m {
|
||||||
|
result[i] = match[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Asura) GetHtml(url string) (html string, err error) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
|
||||||
|
// TODO: Testing for above 300 is dirty
|
||||||
|
if err != nil && resp.StatusCode > 300 {
|
||||||
|
return "", errors.New("could not get html")
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err := Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Could not close body because: %v\n", err)
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
all, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
h := string(all)
|
||||||
|
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Asura) GetNext(html string) (url string, err error) {
|
||||||
|
//TODO implement me
|
||||||
|
return "#/next/", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Asura) GetPrev(html string) (url string, err error) {
|
||||||
|
//TODO implement me
|
||||||
|
return "#/prev/", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Asura) GetTitleAndChapter(url string) (title string, chapter string, err error) {
|
||||||
|
//TODO implement me
|
||||||
|
reg, err := regexp.Compile(`\d*-(.*?)-(\d*)/`)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := reg.FindAllStringSubmatch(url, -1)
|
||||||
|
if len(matches) <= 0 {
|
||||||
|
return "", "", errors.New("no title or chapter found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches[0][1], matches[0][2], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Asura) GetTitleIdAndChapterId(url string) (titleId int, chapterId int, err error) {
|
||||||
|
//TODO implement me
|
||||||
|
reg, err := regexp.Compile(`(\d*)-.*?-(\d*)/`)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := reg.FindAllStringSubmatch(url, -1)
|
||||||
|
if len(matches) <= 0 {
|
||||||
|
return 0, 0, errors.New("no title or chapter found")
|
||||||
|
}
|
||||||
|
t, err := strconv.Atoi(matches[0][1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
c, err := strconv.Atoi(matches[0][2])
|
||||||
|
|
||||||
|
return t, c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Asura) GetThumbnail(mangaId string) (thumbnailUrl string, err error) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
18
internal/provider/available_providers.go
Normal file
18
internal/provider/available_providers.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
type ProviderType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
BatoId ProviderType = iota
|
||||||
|
AsuraId ProviderType = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetProviderByType(typeId ProviderType) Provider {
|
||||||
|
switch typeId {
|
||||||
|
case BatoId:
|
||||||
|
return &Bato{}
|
||||||
|
case AsuraId:
|
||||||
|
return &Asura{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -7,19 +7,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bato struct{}
|
type Bato struct{}
|
||||||
|
|
||||||
func (b *Bato) CleanUrlToSub(url string) string {
|
|
||||||
trimmed := strings.TrimPrefix(url, "https://bato.to/title")
|
|
||||||
trimmed = strings.Trim(trimmed, "/")
|
|
||||||
return trimmed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bato) GetImageList(html string) ([]string, error) {
|
func (b *Bato) GetImageList(html string) ([]string, error) {
|
||||||
reg, err := regexp.Compile(`<astro-island.*props=".*;imageFiles":\[1,"\[(.*)]"]`)
|
reg, err := regexp.Compile(`<astro-island.*props=".*;imageFiles":\[1,"\[(.*)]"]`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -58,7 +49,7 @@ func (b *Bato) GetHtml(titleSubUrl string) (string, error) {
|
|||||||
defer func(Body io.ReadCloser) {
|
defer func(Body io.ReadCloser) {
|
||||||
err := Body.Close()
|
err := Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not close http body")
|
fmt.Printf("Could not close body because: %v\n", err)
|
||||||
}
|
}
|
||||||
}(resp.Body)
|
}(resp.Body)
|
||||||
|
|
||||||
@@ -73,23 +64,14 @@ func (b *Bato) GetHtml(titleSubUrl string) (string, error) {
|
|||||||
|
|
||||||
func (b *Bato) GetNext(html string) (subUrl string, err error) {
|
func (b *Bato) GetNext(html string) (subUrl string, err error) {
|
||||||
reg, err := regexp.Compile(`<a data-hk="0-6-0" .*? href="(.*?)["']`)
|
reg, err := regexp.Compile(`<a data-hk="0-6-0" .*? href="(.*?)["']`)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
match := reg.FindStringSubmatch(html)
|
match := reg.FindStringSubmatch(html)
|
||||||
if len(match) <= 1 {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return match[1], err
|
return match[1], err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bato) GetPrev(html string) (subUrl string, err error) {
|
func (b *Bato) GetPrev(html string) (subUrl string, err error) {
|
||||||
reg, err := regexp.Compile(`<a data-hk="0-5-0" .*? href="(.*?)["']`)
|
reg, err := regexp.Compile(`<a data-hk="0-5-0" .*? href="(.*?)["']`)
|
||||||
match := reg.FindStringSubmatch(html)
|
match := reg.FindStringSubmatch(html)
|
||||||
if len(match) <= 1 {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return match[1], err
|
return match[1], err
|
||||||
}
|
}
|
||||||
@@ -127,24 +109,9 @@ func (b *Bato) GetTitleIdAndChapterId(url string) (titleId int, chapterId int, e
|
|||||||
return t, c, err
|
return t, c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bato) GetChapterList(subUrl string) (subUrls []string, err error) {
|
//func (b *Bato) GetChapterList(url string) (chapterIds []int, 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) {
|
func (b *Bato) GetThumbnail(subUrl string) (thumbnailUrl string, err error) {
|
||||||
url := fmt.Sprintf("https://bato.to/title/%s", subUrl)
|
url := fmt.Sprintf("https://bato.to/title/%s", subUrl)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
CleanUrlToSub(url string) string
|
|
||||||
GetImageList(html string) (imageUrls []string, err error)
|
GetImageList(html string) (imageUrls []string, err error)
|
||||||
GetHtml(url string) (html string, err error)
|
GetHtml(url string) (html string, err error)
|
||||||
GetNext(html string) (url string, err error)
|
GetNext(html string) (url string, err error)
|
||||||
@@ -9,5 +8,4 @@ type Provider interface {
|
|||||||
GetTitleAndChapter(url string) (title string, chapter string, err error)
|
GetTitleAndChapter(url string) (title string, chapter string, err error)
|
||||||
GetTitleIdAndChapterId(url string) (titleId int, chapterId int, err error)
|
GetTitleIdAndChapterId(url string) (titleId int, chapterId int, err error)
|
||||||
GetThumbnail(mangaId string) (thumbnailUrl string, err error)
|
GetThumbnail(mangaId string) (thumbnailUrl string, err error)
|
||||||
GetChapterList(url string) (urls []string, err error)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,194 +1,69 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"mangaGetter/internal/database"
|
||||||
|
"mangaGetter/internal/view"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pablu23/mangaGetter/internal/database"
|
|
||||||
"github.com/pablu23/mangaGetter/internal/view"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) HandleDisable(w http.ResponseWriter, r *http.Request) {
|
|
||||||
id := r.PostFormValue("mangaId")
|
|
||||||
var manga database.Manga
|
|
||||||
s.DbMgr.Db.Where("id = ?", id).First(&manga)
|
|
||||||
|
|
||||||
if manga.Enabled {
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
|
||||||
} else {
|
|
||||||
http.Redirect(w, r, "/archive", http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
manga.Enabled = !manga.Enabled
|
|
||||||
s.DbMgr.Db.Save(&manga)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) HandleUpdate(w http.ResponseWriter, r *http.Request) {
|
|
||||||
s.UpdateMangaList()
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) HandleLoginPost(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if s.options.Auth.Enabled {
|
|
||||||
auth := s.options.Auth.Get()
|
|
||||||
secret := r.PostFormValue("secret")
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: secret,
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: auth.MaxAge,
|
|
||||||
Secure: auth.Secure,
|
|
||||||
HttpOnly: false,
|
|
||||||
SameSite: http.SameSiteLaxMode,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
|
||||||
tmpl := template.Must(view.GetViewTemplate(view.Login))
|
|
||||||
tmpl.Execute(w, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
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.CurrSubUrl = url
|
|
||||||
s.PrevSubUrl = ""
|
|
||||||
s.NextSubUrl = ""
|
|
||||||
s.LoadCurr()
|
|
||||||
|
|
||||||
go s.LoadNext()
|
|
||||||
go s.LoadPrev()
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/current/", http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) HandleArchive(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var all []*database.Manga
|
|
||||||
_ = s.DbMgr.Db.Preload("Chapters").Where("enabled = 0").Find(&all)
|
|
||||||
|
|
||||||
var tmp []database.Setting
|
|
||||||
s.DbMgr.Db.Find(&tmp)
|
|
||||||
settings := make(map[string]database.Setting)
|
|
||||||
for _, m := range tmp {
|
|
||||||
settings[m.Name] = m
|
|
||||||
}
|
|
||||||
|
|
||||||
s.ViewMenu(w, all, settings, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) HandleMenu(w http.ResponseWriter, _ *http.Request) {
|
func (s *Server) HandleMenu(w http.ResponseWriter, _ *http.Request) {
|
||||||
var all []*database.Manga
|
|
||||||
_ = s.DbMgr.Db.Preload("Chapters").Where("enabled = 1").Find(&all)
|
|
||||||
|
|
||||||
var tmp []database.Setting
|
|
||||||
s.DbMgr.Db.Find(&tmp)
|
|
||||||
settings := make(map[string]database.Setting)
|
|
||||||
for _, m := range tmp {
|
|
||||||
settings[m.Name] = m
|
|
||||||
}
|
|
||||||
|
|
||||||
s.ViewMenu(w, all, settings, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) ViewMenu(w http.ResponseWriter, mangas []*database.Manga, settings map[string]database.Setting, archive bool) {
|
|
||||||
tmpl := template.Must(view.GetViewTemplate(view.Menu))
|
tmpl := template.Must(view.GetViewTemplate(view.Menu))
|
||||||
|
|
||||||
l := len(mangas)
|
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)
|
||||||
mangaViewModels := make([]view.MangaViewModel, l)
|
mangaViewModels := make([]view.MangaViewModel, l)
|
||||||
counter := 0
|
counter := 0
|
||||||
|
|
||||||
//TODO: Change all this to be more performant
|
for _, manga := range all {
|
||||||
for _, manga := range mangas {
|
|
||||||
title := cases.Title(language.English, cases.Compact).String(strings.Replace(manga.Title, "-", " ", -1))
|
title := cases.Title(language.English, cases.Compact).String(strings.Replace(manga.Title, "-", " ", -1))
|
||||||
|
|
||||||
thumbnail, updated, err := s.LoadThumbnail(manga)
|
thumbnail, err := s.LoadThumbnail(manga.Id)
|
||||||
//TODO: Add default picture instead of not showing Manga at all
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if updated {
|
manga.Thumbnail = s.ImageBuffers[thumbnail]
|
||||||
s.DbMgr.Db.Save(manga)
|
|
||||||
}
|
|
||||||
// This is very slow
|
|
||||||
// TODO: put this into own Method
|
|
||||||
if manga.LastChapterNum == "" {
|
|
||||||
err, updated := s.UpdateLatestAvailableChapter(manga)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not update latest available chapters")
|
|
||||||
}
|
|
||||||
if updated {
|
|
||||||
s.DbMgr.Db.Save(manga)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
latestChapter, ok := manga.GetLatestChapter()
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mangaViewModels[counter] = view.MangaViewModel{
|
mangaViewModels[counter] = view.MangaViewModel{
|
||||||
ID: manga.Id,
|
ID: manga.Id,
|
||||||
Title: title,
|
Title: title,
|
||||||
Number: latestChapter.Number,
|
Number: manga.LatestChapter.Number,
|
||||||
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: latestChapter.Url,
|
Url: manga.LatestChapter.Url,
|
||||||
ThumbnailUrl: thumbnail,
|
ThumbnailUrl: thumbnail,
|
||||||
Enabled: manga.Enabled,
|
|
||||||
}
|
}
|
||||||
counter++
|
counter++
|
||||||
}
|
}
|
||||||
|
|
||||||
order, ok := settings["order"]
|
|
||||||
if !ok || order.Value == "title" {
|
|
||||||
slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int {
|
slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int {
|
||||||
return cmp.Compare(a.Title, b.Title)
|
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
menuViewModel := view.MenuViewModel{
|
menuViewModel := view.MenuViewModel{
|
||||||
Settings: settings,
|
|
||||||
Mangas: mangaViewModels,
|
Mangas: mangaViewModels,
|
||||||
Archive: archive,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := tmpl.Execute(w, menuViewModel)
|
err := tmpl.Execute(w, menuViewModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not template Menu")
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,86 +71,93 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
mangaStr := r.PostFormValue("mangaId")
|
mangaStr := r.PostFormValue("mangaId")
|
||||||
|
|
||||||
if mangaStr == "" {
|
if mangaStr == "" {
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mangaId, err := strconv.Atoi(mangaStr)
|
mangaId, err := strconv.Atoi(mangaStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("Id", mangaStr).Msg("Could not convert id to int")
|
fmt.Println(err)
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.DbMgr.Delete(mangaId)
|
err = s.DbMgr.Delete(mangaId)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) HandleExit(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) HandleExit(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
err := s.DbMgr.Save()
|
||||||
|
|
||||||
go func() {
|
|
||||||
s.Mutex.Lock()
|
|
||||||
if s.PrevViewModel != nil {
|
|
||||||
for _, img := range s.PrevViewModel.Images {
|
|
||||||
delete(s.ImageBuffers, img.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.CurrViewModel != nil {
|
|
||||||
|
|
||||||
for _, img := range s.CurrViewModel.Images {
|
|
||||||
delete(s.ImageBuffers, img.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.NextViewModel != nil {
|
|
||||||
|
|
||||||
for _, img := range s.NextViewModel.Images {
|
|
||||||
delete(s.ImageBuffers, img.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Mutex.Unlock()
|
|
||||||
log.Info().Msg("Cleaned up images")
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) HandleCurrent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
tmpl := template.Must(view.GetViewTemplate(view.Viewer))
|
|
||||||
mangaId, chapterId, err := s.Provider.GetTitleIdAndChapterId(s.CurrSubUrl)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("subUrl", s.CurrSubUrl).Msg("Could not get TitleId and ChapterId")
|
fmt.Println(err)
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
title, chapterName, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) HandleCurrent(w http.ResponseWriter, _ *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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("subUrl", s.CurrSubUrl).Msg("Could not get Title and Chapter")
|
fmt.Println(err)
|
||||||
}
|
|
||||||
|
|
||||||
var manga database.Manga
|
|
||||||
result := s.DbMgr.Db.First(&manga, mangaId)
|
|
||||||
if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
manga = database.NewManga(mangaId, title, time.Now().Unix())
|
|
||||||
} else {
|
} else {
|
||||||
manga.TimeStampUnix = time.Now().Unix()
|
title, chapter, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
||||||
}
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
var chapter database.Chapter
|
|
||||||
result = s.DbMgr.Db.First(&chapter, chapterId)
|
|
||||||
if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
chapterNumberStr := strings.Replace(chapterName, "ch_", "", 1)
|
|
||||||
chapter = database.NewChapter(chapterId, mangaId, s.CurrSubUrl, chapterName, chapterNumberStr, time.Now().Unix())
|
|
||||||
} else {
|
} else {
|
||||||
chapter.TimeStampUnix = time.Now().Unix()
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
s.DbMgr.Db.Save(&manga)
|
if s.DbMgr.Chapters[chapterId] == nil {
|
||||||
s.DbMgr.Db.Save(&chapter)
|
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)
|
err = tmpl.Execute(w, s.CurrViewModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not template Current")
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,30 +167,32 @@ func (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer s.Mutex.Unlock()
|
defer s.Mutex.Unlock()
|
||||||
buf := s.ImageBuffers[u]
|
buf := s.ImageBuffers[u]
|
||||||
if buf == nil {
|
if buf == nil {
|
||||||
log.Warn().Str("url", u).Msg("Image not found")
|
fmt.Printf("url: %s is nil\n", u)
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "image/webp")
|
w.Header().Set("Content-Type", "image/webp")
|
||||||
_, err := w.Write(buf)
|
_, err := w.Write(buf.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not write image")
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed favicon.ico
|
//go:embed favicon.ico
|
||||||
var ico []byte
|
var ico []byte
|
||||||
|
|
||||||
func (s *Server) HandleFavicon(w http.ResponseWriter, _ *http.Request) {
|
func (s *Server) HandleFavicon(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "image/webp")
|
w.Header().Set("Content-Type", "image/webp")
|
||||||
_, err := w.Write(ico)
|
_, err := w.Write(ico)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not write favicon")
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) HandleNext(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) HandleNext(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Println("Received Next")
|
||||||
|
|
||||||
if s.PrevViewModel != nil {
|
if s.PrevViewModel != nil {
|
||||||
go func(viewModel view.ImageViewModel, s *Server) {
|
go func(viewModel view.ImageViewModel, s *Server) {
|
||||||
s.Mutex.Lock()
|
s.Mutex.Lock()
|
||||||
@@ -316,12 +200,16 @@ func (s *Server) HandleNext(w http.ResponseWriter, r *http.Request) {
|
|||||||
delete(s.ImageBuffers, img.Path)
|
delete(s.ImageBuffers, img.Path)
|
||||||
}
|
}
|
||||||
s.Mutex.Unlock()
|
s.Mutex.Unlock()
|
||||||
log.Debug().Msg("Cleaned imagebuffer")
|
fmt.Println("Cleaned out of scope Last")
|
||||||
}(*s.PrevViewModel, s)
|
}(*s.PrevViewModel, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.NextViewModel == nil || s.NextSubUrl == "" {
|
if s.NextViewModel == nil || s.NextSubUrl == "" {
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
|
err := s.DbMgr.Save()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,10 +220,10 @@ func (s *Server) HandleNext(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
go s.LoadNext()
|
go s.LoadNext()
|
||||||
|
|
||||||
http.Redirect(w, r, "/current/", http.StatusFound)
|
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) HandlePrev(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) HandlePrev(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Println("Received Prev")
|
||||||
if s.NextViewModel != nil {
|
if s.NextViewModel != nil {
|
||||||
go func(viewModel view.ImageViewModel, s *Server) {
|
go func(viewModel view.ImageViewModel, s *Server) {
|
||||||
s.Mutex.Lock()
|
s.Mutex.Lock()
|
||||||
@@ -343,12 +231,16 @@ func (s *Server) HandlePrev(w http.ResponseWriter, r *http.Request) {
|
|||||||
delete(s.ImageBuffers, img.Path)
|
delete(s.ImageBuffers, img.Path)
|
||||||
}
|
}
|
||||||
s.Mutex.Unlock()
|
s.Mutex.Unlock()
|
||||||
log.Debug().Msg("Cleaned imagebuffer")
|
fmt.Println("Cleaned out of scope Last")
|
||||||
}(*s.NextViewModel, s)
|
}(*s.NextViewModel, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.PrevViewModel == nil || s.PrevSubUrl == "" {
|
if s.PrevViewModel == nil || s.PrevSubUrl == "" {
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
|
err := s.DbMgr.Save()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,51 +251,15 @@ func (s *Server) HandlePrev(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
go s.LoadPrev()
|
go s.LoadPrev()
|
||||||
|
|
||||||
http.Redirect(w, r, "/current/", http.StatusFound)
|
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.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 if res.Error != nil {
|
|
||||||
log.Error().Err(res.Error).Send()
|
|
||||||
} else {
|
|
||||||
s.DbMgr.Db.Model(&setting).Update("value", settingValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) HandleNewQuery(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) HandleNewQuery(w http.ResponseWriter, r *http.Request) {
|
||||||
sub := r.PostFormValue("subUrl")
|
sub := r.PostFormValue("subUrl")
|
||||||
|
|
||||||
sub = s.Provider.CleanUrlToSub(sub)
|
s.Mutex.Lock()
|
||||||
url := fmt.Sprintf("/title/%s", sub)
|
s.ImageBuffers = make(map[string]*bytes.Buffer)
|
||||||
|
s.Mutex.Unlock()
|
||||||
s.CurrSubUrl = url
|
s.CurrSubUrl = url
|
||||||
s.PrevSubUrl = ""
|
s.PrevSubUrl = ""
|
||||||
s.NextSubUrl = ""
|
s.NextSubUrl = ""
|
||||||
@@ -412,5 +268,5 @@ func (s *Server) HandleNewQuery(w http.ResponseWriter, r *http.Request) {
|
|||||||
go s.LoadNext()
|
go s.LoadNext()
|
||||||
go s.LoadPrev()
|
go s.LoadPrev()
|
||||||
|
|
||||||
http.Redirect(w, r, "/current/", http.StatusFound)
|
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) Auth(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
cookie, _ := r.Cookie("auth")
|
|
||||||
if r.URL.Path == "/login" || r.URL.Path == "/login/" {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.secret == "" || (cookie != nil && cookie.Value == s.secret) {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
http.Redirect(w, r, "/login", http.StatusFound)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
Port int
|
|
||||||
Auth Optional[AuthOptions]
|
|
||||||
Tls Optional[TlsOptions]
|
|
||||||
UpdateInterval time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type Optional[v any] struct {
|
|
||||||
Enabled bool
|
|
||||||
value v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Optional[v]) Get() v {
|
|
||||||
return o.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Optional[v]) Set(value v) {
|
|
||||||
o.value = value
|
|
||||||
o.Enabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Optional[v]) Apply(apply func(*v)) {
|
|
||||||
o.Enabled = true
|
|
||||||
apply(&o.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Raw AuthType = iota
|
|
||||||
File
|
|
||||||
)
|
|
||||||
|
|
||||||
type AuthOptions struct {
|
|
||||||
// Secret Direct or Path to secret File
|
|
||||||
Secret string
|
|
||||||
LoadType AuthType
|
|
||||||
Secure bool
|
|
||||||
MaxAge int
|
|
||||||
}
|
|
||||||
|
|
||||||
type TlsOptions struct {
|
|
||||||
CertPath string
|
|
||||||
KeyPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDefaultOptions() Options {
|
|
||||||
return Options{
|
|
||||||
Port: 8080,
|
|
||||||
Auth: Optional[AuthOptions]{
|
|
||||||
Enabled: false,
|
|
||||||
},
|
|
||||||
Tls: Optional[TlsOptions]{
|
|
||||||
Enabled: false,
|
|
||||||
},
|
|
||||||
UpdateInterval: 15 * time.Minute,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,178 +2,67 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
"io"
|
"io"
|
||||||
|
"mangaGetter/internal/database"
|
||||||
|
"mangaGetter/internal/view"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pablu23/mangaGetter/internal/database"
|
|
||||||
"github.com/pablu23/mangaGetter/internal/provider"
|
|
||||||
"github.com/pablu23/mangaGetter/internal/view"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
ContextManga *database.Manga
|
||||||
PrevViewModel *view.ImageViewModel
|
PrevViewModel *view.ImageViewModel
|
||||||
CurrViewModel *view.ImageViewModel
|
CurrViewModel *view.ImageViewModel
|
||||||
NextViewModel *view.ImageViewModel
|
NextViewModel *view.ImageViewModel
|
||||||
|
|
||||||
ImageBuffers map[string][]byte
|
ImageBuffers map[string]*bytes.Buffer
|
||||||
Mutex *sync.Mutex
|
Mutex *sync.Mutex
|
||||||
|
|
||||||
NextSubUrl string
|
NextSubUrl string
|
||||||
CurrSubUrl string
|
CurrSubUrl string
|
||||||
PrevSubUrl string
|
PrevSubUrl string
|
||||||
|
|
||||||
Provider provider.Provider
|
|
||||||
|
|
||||||
IsFirst bool
|
IsFirst bool
|
||||||
IsLast bool
|
IsLast bool
|
||||||
|
|
||||||
DbMgr *database.Manager
|
DbMgr *database.Manager
|
||||||
|
|
||||||
mux *http.ServeMux
|
|
||||||
|
|
||||||
options Options
|
|
||||||
secret string
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(provider provider.Provider, db *database.Manager, mux *http.ServeMux, options ...func(*Options)) *Server {
|
|
||||||
opts := NewDefaultOptions()
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(&opts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func New(db *database.Manager) *Server {
|
||||||
s := Server{
|
s := Server{
|
||||||
ImageBuffers: make(map[string][]byte),
|
ImageBuffers: make(map[string]*bytes.Buffer),
|
||||||
Provider: provider,
|
|
||||||
DbMgr: db,
|
DbMgr: db,
|
||||||
Mutex: &sync.Mutex{},
|
Mutex: &sync.Mutex{},
|
||||||
mux: mux,
|
|
||||||
options: opts,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RegisterRoutes() {
|
|
||||||
s.mux.HandleFunc("GET /login", s.HandleLogin)
|
|
||||||
s.mux.HandleFunc("POST /login", s.HandleLoginPost)
|
|
||||||
s.mux.HandleFunc("/", s.HandleMenu)
|
|
||||||
s.mux.HandleFunc("/new/", s.HandleNewQuery)
|
|
||||||
s.mux.HandleFunc("/new/title/{title}/{chapter}", s.HandleNew)
|
|
||||||
s.mux.HandleFunc("/current/", s.HandleCurrent)
|
|
||||||
s.mux.HandleFunc("/img/{url}", s.HandleImage)
|
|
||||||
s.mux.HandleFunc("POST /next", s.HandleNext)
|
|
||||||
s.mux.HandleFunc("POST /prev", s.HandlePrev)
|
|
||||||
s.mux.HandleFunc("POST /exit", s.HandleExit)
|
|
||||||
s.mux.HandleFunc("POST /delete", s.HandleDelete)
|
|
||||||
s.mux.HandleFunc("/favicon.ico", s.HandleFavicon)
|
|
||||||
s.mux.HandleFunc("POST /setting/", s.HandleSetting)
|
|
||||||
s.mux.HandleFunc("GET /setting/set/{setting}/{value}", s.HandleSettingSet)
|
|
||||||
s.mux.HandleFunc("GET /update", s.HandleUpdate)
|
|
||||||
s.mux.HandleFunc("POST /disable", s.HandleDisable)
|
|
||||||
s.mux.HandleFunc("GET /archive", s.HandleArchive)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
|
||||||
server := http.Server{
|
|
||||||
Addr: fmt.Sprintf(":%d", s.options.Port),
|
|
||||||
Handler: s.mux,
|
|
||||||
}
|
|
||||||
s.RegisterRoutes()
|
|
||||||
s.registerUpdater()
|
|
||||||
|
|
||||||
if s.options.Auth.Enabled {
|
|
||||||
auth := s.options.Auth.Get()
|
|
||||||
switch auth.LoadType {
|
|
||||||
case Raw:
|
|
||||||
s.secret = auth.Secret
|
|
||||||
case File:
|
|
||||||
secretBytes, err := os.ReadFile(auth.Secret)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.secret = string(secretBytes)
|
|
||||||
}
|
|
||||||
s.secret = strings.TrimSpace(s.secret)
|
|
||||||
server.Handler = s.Auth(s.mux)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.options.Tls.Enabled {
|
|
||||||
tlsOpts := s.options.Tls.Get()
|
|
||||||
server.TLSConfig = &tls.Config{
|
|
||||||
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
cert, err := tls.LoadX509KeyPair(tlsOpts.CertPath, tlsOpts.KeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &cert, err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
log.Info().Int("Port", s.options.Port).Str("Cert", tlsOpts.CertPath).Str("Key", tlsOpts.KeyPath).Msg("Starting server")
|
|
||||||
return server.ListenAndServeTLS("", "")
|
|
||||||
} else {
|
|
||||||
log.Info().Int("Port", s.options.Port).Msg("Starting server")
|
|
||||||
return server.ListenAndServe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) UpdateMangaList() {
|
|
||||||
var all []*database.Manga
|
|
||||||
s.DbMgr.Db.Where("enabled = 1").Find(&all)
|
|
||||||
for _, m := range all {
|
|
||||||
err, updated := s.UpdateLatestAvailableChapter(m)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Str("Manga", m.Title).Msg("Could not update latest available chapters")
|
|
||||||
}
|
|
||||||
if updated {
|
|
||||||
s.DbMgr.Db.Save(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) registerUpdater() {
|
|
||||||
if s.options.UpdateInterval > 0 {
|
|
||||||
log.Info().Str("Interval", s.options.UpdateInterval.String()).Msg("Registering Updater")
|
|
||||||
go func(s *Server) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-time.After(s.options.UpdateInterval):
|
|
||||||
s.UpdateMangaList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) LoadNext() {
|
func (s *Server) LoadNext() {
|
||||||
c, err := s.Provider.GetHtml(s.CurrSubUrl)
|
c, err := s.ContextManga.Provider.GetHtml(s.CurrSubUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not get Html for current chapter")
|
fmt.Println(err)
|
||||||
s.NextSubUrl = ""
|
s.NextSubUrl = ""
|
||||||
s.NextViewModel = nil
|
s.NextViewModel = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
next, err := s.Provider.GetNext(c)
|
next, err := s.ContextManga.Provider.GetNext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not load next chapter")
|
fmt.Println(err)
|
||||||
s.NextSubUrl = ""
|
s.NextSubUrl = ""
|
||||||
s.NextViewModel = nil
|
s.NextViewModel = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
html, err := s.Provider.GetHtml(next)
|
html, err := s.ContextManga.Provider.GetHtml(next)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not get Html for next chapter")
|
fmt.Println(err)
|
||||||
s.NextSubUrl = ""
|
s.NextSubUrl = ""
|
||||||
s.NextViewModel = nil
|
s.NextViewModel = nil
|
||||||
return
|
return
|
||||||
@@ -181,15 +70,14 @@ func (s *Server) LoadNext() {
|
|||||||
|
|
||||||
imagesNext, err := s.AppendImagesToBuf(html)
|
imagesNext, err := s.AppendImagesToBuf(html)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not download images")
|
fmt.Println(err)
|
||||||
s.NextSubUrl = ""
|
s.NextSubUrl = ""
|
||||||
s.NextViewModel = nil
|
s.NextViewModel = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
title, chapter, err := s.Provider.GetTitleAndChapter(next)
|
title, chapter, err := s.ContextManga.Provider.GetTitleAndChapter(next)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("Url", next).Msg("Could not extract title and chapter")
|
|
||||||
title = "Unknown"
|
title = "Unknown"
|
||||||
chapter = "ch_?"
|
chapter = "ch_?"
|
||||||
}
|
}
|
||||||
@@ -198,27 +86,27 @@ func (s *Server) LoadNext() {
|
|||||||
|
|
||||||
s.NextViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
s.NextViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
||||||
s.NextSubUrl = next
|
s.NextSubUrl = next
|
||||||
log.Debug().Msg("Successfully loaded next chapter")
|
fmt.Println("Loaded next")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) LoadPrev() {
|
func (s *Server) LoadPrev() {
|
||||||
c, err := s.Provider.GetHtml(s.CurrSubUrl)
|
c, err := s.ContextManga.Provider.GetHtml(s.CurrSubUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not get Html for current chapter")
|
fmt.Println(err)
|
||||||
s.PrevSubUrl = ""
|
s.PrevSubUrl = ""
|
||||||
s.PrevViewModel = nil
|
s.PrevViewModel = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
prev, err := s.Provider.GetPrev(c)
|
prev, err := s.ContextManga.Provider.GetPrev(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not load prev chapter")
|
fmt.Println(err)
|
||||||
s.PrevSubUrl = ""
|
s.PrevSubUrl = ""
|
||||||
s.PrevViewModel = nil
|
s.PrevViewModel = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
html, err := s.Provider.GetHtml(prev)
|
html, err := s.ContextManga.Provider.GetHtml(prev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not get Html for prev chapter")
|
fmt.Println(err)
|
||||||
s.PrevSubUrl = ""
|
s.PrevSubUrl = ""
|
||||||
s.PrevViewModel = nil
|
s.PrevViewModel = nil
|
||||||
return
|
return
|
||||||
@@ -226,15 +114,14 @@ func (s *Server) LoadPrev() {
|
|||||||
|
|
||||||
imagesNext, err := s.AppendImagesToBuf(html)
|
imagesNext, err := s.AppendImagesToBuf(html)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not download images")
|
fmt.Println(err)
|
||||||
s.PrevSubUrl = ""
|
s.PrevSubUrl = ""
|
||||||
s.PrevViewModel = nil
|
s.PrevViewModel = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
title, chapter, err := s.Provider.GetTitleAndChapter(prev)
|
title, chapter, err := s.ContextManga.Provider.GetTitleAndChapter(prev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("Url", prev).Msg("Could not extract title and chapter")
|
|
||||||
title = "Unknown"
|
title = "Unknown"
|
||||||
chapter = "ch_?"
|
chapter = "ch_?"
|
||||||
}
|
}
|
||||||
@@ -244,28 +131,19 @@ func (s *Server) LoadPrev() {
|
|||||||
s.PrevViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
s.PrevViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
||||||
|
|
||||||
s.PrevSubUrl = prev
|
s.PrevSubUrl = prev
|
||||||
log.Debug().Msg("Successfully loaded prev chapter")
|
fmt.Println("Loaded prev")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) LoadCurr() {
|
func (s *Server) LoadCurr() {
|
||||||
html, err := s.Provider.GetHtml(s.CurrSubUrl)
|
html, err := s.ContextManga.Provider.GetHtml(s.CurrSubUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not get Html for current chapter")
|
panic(err)
|
||||||
s.NextSubUrl = ""
|
|
||||||
s.PrevSubUrl = ""
|
|
||||||
s.CurrSubUrl = ""
|
|
||||||
s.NextViewModel = nil
|
|
||||||
s.CurrViewModel = nil
|
|
||||||
s.PrevViewModel = nil
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
imagesCurr, err := s.AppendImagesToBuf(html)
|
imagesCurr, err := s.AppendImagesToBuf(html)
|
||||||
|
|
||||||
title, chapter, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
title, chapter, err := s.ContextManga.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("Url", s.CurrSubUrl).Msg("Could not extract title and chapter")
|
|
||||||
title = "Unknown"
|
title = "Unknown"
|
||||||
chapter = "ch_?"
|
chapter = "ch_?"
|
||||||
}
|
}
|
||||||
@@ -273,62 +151,32 @@ func (s *Server) LoadCurr() {
|
|||||||
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
||||||
|
|
||||||
s.CurrViewModel = &view.ImageViewModel{Images: imagesCurr, Title: full}
|
s.CurrViewModel = &view.ImageViewModel{Images: imagesCurr, Title: full}
|
||||||
log.Debug().Msg("Successfully loaded curr chapter")
|
fmt.Println("Loaded current")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) UpdateLatestAvailableChapter(manga *database.Manga) (error, bool) {
|
func (s *Server) LoadThumbnail(mangaId int) (path string, err error) {
|
||||||
log.Info().Str("Manga", manga.Title).Msg("Updating Manga")
|
strId := strconv.Itoa(mangaId)
|
||||||
|
|
||||||
l, err := s.Provider.GetChapterList("/title/" + strconv.Itoa(manga.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.LastChapterNum == chapterNumberStr {
|
|
||||||
return nil, false
|
|
||||||
} else {
|
|
||||||
manga.LastChapterNum = chapterNumberStr
|
|
||||||
return nil, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) LoadThumbnail(manga *database.Manga) (path string, updated bool, err error) {
|
|
||||||
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, false, nil
|
return strId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if manga.Thumbnail != nil {
|
url, err := s.ContextManga.Provider.GetThumbnail(strconv.Itoa(mangaId))
|
||||||
s.ImageBuffers[strId] = manga.Thumbnail
|
|
||||||
return strId, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := s.Provider.GetThumbnail(strId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", err
|
||||||
}
|
}
|
||||||
ram, err := addFileToRam(url)
|
ram, err := addFileToRam(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", err
|
||||||
}
|
}
|
||||||
manga.Thumbnail = ram
|
|
||||||
s.ImageBuffers[strId] = ram
|
s.ImageBuffers[strId] = ram
|
||||||
return strId, true, nil
|
return strId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AppendImagesToBuf(html string) ([]view.Image, error) {
|
func (s *Server) AppendImagesToBuf(html string) ([]view.Image, error) {
|
||||||
imgList, err := s.Provider.GetImageList(html)
|
imgList, err := s.ContextManga.Provider.GetImageList(html)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -343,11 +191,11 @@ func (s *Server) AppendImagesToBuf(html string) ([]view.Image, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
name := filepath.Base(url)
|
g := uuid.New()
|
||||||
s.Mutex.Lock()
|
s.Mutex.Lock()
|
||||||
s.ImageBuffers[name] = buf
|
s.ImageBuffers[g.String()] = buf
|
||||||
s.Mutex.Unlock()
|
s.Mutex.Unlock()
|
||||||
images[i] = view.Image{Path: name, Index: i}
|
images[i] = view.Image{Path: g.String(), Index: i}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}(i, url, &wg)
|
}(i, url, &wg)
|
||||||
}
|
}
|
||||||
@@ -356,7 +204,7 @@ func (s *Server) AppendImagesToBuf(html string) ([]view.Image, error) {
|
|||||||
return images, nil
|
return images, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addFileToRam(url string) ([]byte, error) {
|
func addFileToRam(url string) (*bytes.Buffer, error) {
|
||||||
// Get the data
|
// Get the data
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -365,7 +213,7 @@ func addFileToRam(url string) ([]byte, error) {
|
|||||||
defer func(Body io.ReadCloser) {
|
defer func(Body io.ReadCloser) {
|
||||||
err := Body.Close()
|
err := Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Could not close http body")
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
}(resp.Body)
|
}(resp.Body)
|
||||||
|
|
||||||
@@ -373,5 +221,5 @@ func addFileToRam(url string) ([]byte, error) {
|
|||||||
|
|
||||||
// Write the body to file
|
// Write the body to file
|
||||||
_, err = io.Copy(buf, resp.Body)
|
_, err = io.Copy(buf, resp.Body)
|
||||||
return buf.Bytes(), err
|
return buf, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<!--suppress CssUnusedSymbol -->
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: rgba(10, 11, 15, 255);
|
|
||||||
margin: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
background-color: rgba(10, 11, 15, 255);
|
|
||||||
border: 1px solid rgba(104, 85, 224, 1);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
width: 280px;
|
|
||||||
height: 30px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
input:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
#formcontainer {
|
|
||||||
border-radius: 1rem;
|
|
||||||
color: rgb(104, 85, 224);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 30px;
|
|
||||||
background-color: rgba(16, 17, 22, 255);
|
|
||||||
height: 40%;
|
|
||||||
width: 30%;
|
|
||||||
box-shadow: 5px 5px 15px 5px rgba(0, 0, 0, 0.34);
|
|
||||||
}
|
|
||||||
#loginbutton {
|
|
||||||
color: rgb(104, 85, 224);
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
width: 300px;
|
|
||||||
height: 50px;
|
|
||||||
transition: 0.4s;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
#loginbutton:hover {
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 0 20px rgba(104, 85, 224, 0.6);
|
|
||||||
background-color: rgba(104, 85, 224, 1);
|
|
||||||
}
|
|
||||||
#passwordinput {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
#passwordinputbox {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="formcontainer">
|
|
||||||
<form method="post" action="/login">
|
|
||||||
<div id="passwordinputbox">
|
|
||||||
<label id="passwordlabel"> Password: </label>
|
|
||||||
<input id="passwordinput" type="password" name="secret" />
|
|
||||||
</div>
|
|
||||||
<input id="loginbutton" type="submit" value="Login" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<!--suppress CssUnusedSymbol -->
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{{if .Archive}}Archive{{else}}Main Menu{{end}}</title>
|
<title>Main Menu</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@@ -14,16 +12,11 @@
|
|||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark-mode {
|
||||||
background-color: #171717;
|
background-color: #171717;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.white {
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-36 {
|
.button-36 {
|
||||||
background-image: linear-gradient(92.88deg, #455EB5 9.16%, #5643CC 43.89%, #673FD7 64.72%);
|
background-image: linear-gradient(92.88deg, #455EB5 9.16%, #5643CC 43.89%, #673FD7 64.72%);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -54,7 +47,6 @@
|
|||||||
.button-36 {
|
.button-36 {
|
||||||
padding: 0 2.6rem;
|
padding: 0 2.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-delete{
|
.button-delete{
|
||||||
padding: 0 2.6rem;
|
padding: 0 2.6rem;
|
||||||
}
|
}
|
||||||
@@ -91,14 +83,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail{
|
.thumbnail{
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd; /* Gray border */
|
||||||
/* Gray border */
|
border-radius: 4px; /* Rounded border */
|
||||||
border-radius: 4px;
|
padding: 5px; /* Some padding */
|
||||||
/* Rounded border */
|
width: 150px; /* Set a small width */
|
||||||
padding: 5px;
|
|
||||||
/* Some padding */
|
|
||||||
width: 150px;
|
|
||||||
/* Set a small width */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail:hover{
|
.thumbnail:hover{
|
||||||
@@ -113,22 +101,17 @@
|
|||||||
text-align: center;
|
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>
|
</style>
|
||||||
</head>
|
<script>
|
||||||
|
function myFunction() {
|
||||||
|
var element = document.body;
|
||||||
|
element.classList.toggle("dark-mode");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<body class='{{(index .Settings "theme").Value}}'>
|
</head>
|
||||||
|
<body>
|
||||||
<form method="post" action="/new/">
|
<form method="post" action="/new/">
|
||||||
<label>
|
<label>
|
||||||
New Sub Url
|
New Sub Url
|
||||||
@@ -136,42 +119,15 @@
|
|||||||
</label>
|
</label>
|
||||||
<input type="submit" value="Open" class="button-36">
|
<input type="submit" value="Open" class="button-36">
|
||||||
</form>
|
</form>
|
||||||
|
<button onclick="myFunction()">Toggle dark mode</button>
|
||||||
<a href='{{if .Archive}}/{{else}}/archive{{end}}'>
|
|
||||||
<button class="button-36">
|
|
||||||
{{if .Archive}}
|
|
||||||
To Main Menu
|
|
||||||
{{else}}
|
|
||||||
To Archive
|
|
||||||
{{end}}
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
{{if not .Archive}}
|
|
||||||
<a href="/update">
|
|
||||||
<button class="button-36">
|
|
||||||
Update Chapters
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<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">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Thumbnail</th>
|
<th>Thumbnail</th>
|
||||||
<th class="table-left"><a href="setting/set/order/title">Title</a></th>
|
<th class="table-left">Title</th>
|
||||||
<th><a href="setting/set/order/chapter">Current Chapter</a></th>
|
<th>Current Chapter</th>
|
||||||
<th><a href="setting/set/order/last">Last Accessed</a></th>
|
<th>Last Accessed</th>
|
||||||
<th>Link</th>
|
<th>Link</th>
|
||||||
<th>Disable/Enable</th>
|
|
||||||
<th>Delete</th>
|
<th>Delete</th>
|
||||||
</tr>
|
</tr>
|
||||||
{{range .Mangas}}
|
{{range .Mangas}}
|
||||||
@@ -182,21 +138,15 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-left">{{.Title}}</td>
|
<td class="table-left">{{.Title}}</td>
|
||||||
<td>{{.Number}} / {{.LastNumber}}</td>
|
<td>{{.Number}}</td>
|
||||||
<td>{{.LastTime}}</td>
|
<td>{{.LastTime}}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="/new/{{.Url}}">
|
<a href="/new/{{.Url}}}">
|
||||||
<button class="button-36">
|
<button class="button-36">
|
||||||
To chapter
|
To chapter
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<form method="post" action="/disable">
|
|
||||||
<input type="hidden" name="mangaId" value="{{.ID}}">
|
|
||||||
<input type="submit" class="button-delete" value="{{if .Enabled}}Disable{{else}}Enable{{end}}">
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<form method="post" action="/delete">
|
<form method="post" action="/delete">
|
||||||
<input type="hidden" name="mangaId" value="{{.ID}}">
|
<input type="hidden" name="mangaId" value="{{.ID}}">
|
||||||
@@ -207,5 +157,4 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</table>
|
</table>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -31,12 +31,6 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
.scroll-container img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* I have no clue what css is, jesus christ ...
|
* I have no clue what css is, jesus christ ...
|
||||||
*/
|
*/
|
||||||
@@ -51,18 +45,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-button {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 80px;
|
|
||||||
right: 15px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text{
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-36 {
|
.button-36 {
|
||||||
background-image: linear-gradient(92.88deg, #455EB5 9.16%, #5643CC 43.89%, #673FD7 64.72%);
|
background-image: linear-gradient(92.88deg, #455EB5 9.16%, #5643CC 43.89%, #673FD7 64.72%);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -97,17 +79,13 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1 class="center text">{{.Title}}</h1>
|
<div class="center">
|
||||||
<div class="center" id="top">
|
|
||||||
<form method="post" action="/prev">
|
<form method="post" action="/prev">
|
||||||
<input type="submit" name="Prev" value="Prev" class="button-36">
|
<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="Exit" value="Exit" class="button-36" formaction="/exit">
|
||||||
<input type="submit" name="Next" value="Next" class="button-36" formaction="/next">
|
<input type="submit" name="Next" value="Next" class="button-36" formaction="/next">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<button class="fixed-button">
|
|
||||||
<a href="#top">TOP</a>
|
|
||||||
</button>
|
|
||||||
<div class="scroll-container">
|
<div class="scroll-container">
|
||||||
{{range .Images}}
|
{{range .Images}}
|
||||||
<img src="/img/{{.Path}}" alt="img_{{.Index}}"/>
|
<img src="/img/{{.Path}}" alt="img_{{.Index}}"/>
|
||||||
|
|||||||
@@ -14,17 +14,12 @@ var menu string
|
|||||||
//go:embed Views/viewer.gohtml
|
//go:embed Views/viewer.gohtml
|
||||||
var viewer string
|
var viewer string
|
||||||
|
|
||||||
//go:embed Views/login.gohtml
|
|
||||||
var login string
|
|
||||||
|
|
||||||
func GetViewTemplate(view View) (*template.Template, error) {
|
func GetViewTemplate(view View) (*template.Template, error) {
|
||||||
switch view {
|
switch view {
|
||||||
case Menu:
|
case Menu:
|
||||||
return template.New("menu").Parse(menu)
|
return template.New("menu").Parse(menu)
|
||||||
case Viewer:
|
case Viewer:
|
||||||
return template.New("viewer").Parse(viewer)
|
return template.New("viewer").Parse(viewer)
|
||||||
case Login:
|
|
||||||
return template.New("login").Parse(login)
|
|
||||||
}
|
}
|
||||||
return nil, errors.New("invalid view")
|
return nil, errors.New("invalid view")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ func GetViewTemplate(view View) (*template.Template, error) {
|
|||||||
path = "internal/view/Views/menu.gohtml"
|
path = "internal/view/Views/menu.gohtml"
|
||||||
case Viewer:
|
case Viewer:
|
||||||
path = "internal/view/Views/viewer.gohtml"
|
path = "internal/view/Views/viewer.gohtml"
|
||||||
case Login:
|
|
||||||
path = "internal/view/Views/login.gohtml"
|
|
||||||
}
|
}
|
||||||
return template.ParseFiles(path)
|
return template.ParseFiles(path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package view
|
package view
|
||||||
|
|
||||||
import "github.com/pablu23/mangaGetter/internal/database"
|
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
Path string
|
Path string
|
||||||
Index int
|
Index int
|
||||||
@@ -15,16 +13,12 @@ type ImageViewModel struct {
|
|||||||
type MangaViewModel struct {
|
type MangaViewModel struct {
|
||||||
ID int
|
ID int
|
||||||
Title string
|
Title string
|
||||||
Number string
|
Number int
|
||||||
LastNumber string
|
|
||||||
LastTime string
|
LastTime string
|
||||||
Url string
|
Url string
|
||||||
ThumbnailUrl string
|
ThumbnailUrl string
|
||||||
Enabled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MenuViewModel struct {
|
type MenuViewModel struct {
|
||||||
Archive bool
|
|
||||||
Settings map[string]database.Setting
|
|
||||||
Mangas []MangaViewModel
|
Mangas []MangaViewModel
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,4 @@ type View int
|
|||||||
const (
|
const (
|
||||||
Menu View = iota
|
Menu View = iota
|
||||||
Viewer View = iota
|
Viewer View = iota
|
||||||
Login View = iota
|
|
||||||
)
|
)
|
||||||
|
|||||||
180
main.go
180
main.go
@@ -1,180 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pablu23/mangaGetter/internal/database"
|
|
||||||
"github.com/pablu23/mangaGetter/internal/provider"
|
|
||||||
"github.com/pablu23/mangaGetter/internal/server"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"gopkg.in/natefinch/lumberjack.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
secretFlag = flag.String("secret", "", "Secret to use for Auth")
|
|
||||||
authFlag = flag.Bool("auth", false, "Use Auth, does not need to be set if secret or secret-path is set")
|
|
||||||
secretFilePathFlag = flag.String("secret-path", "", "Path to file with ONLY secret in it")
|
|
||||||
portFlag = flag.Int("port", 80, "The port on which to host")
|
|
||||||
serverFlag = flag.Bool("server", false, "If false dont open Browser with Address")
|
|
||||||
databaseFlag = flag.String("database", "", "Path to sqlite.db file")
|
|
||||||
certFlag = flag.String("cert", "", "Path to cert file, has to be used in conjunction with key")
|
|
||||||
keyFlag = flag.String("key", "", "Path to key file, has to be used in conjunction with cert")
|
|
||||||
updateIntervalFlag = flag.String("update", "0h", "Interval to update Mangas")
|
|
||||||
debugFlag = flag.Bool("debug", false, "Activate debug Logs")
|
|
||||||
prettyLogsFlag = flag.Bool("pretty", false, "Pretty pring Logs")
|
|
||||||
logPathFlag = flag.String("log", "", "Path to logfile, stderr if default")
|
|
||||||
maxAgeFlag = flag.Int("age", 3600, "Max age for login Session")
|
|
||||||
secureFlag = flag.Bool("secure", false, "Cookie secure?")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
setupLogging()
|
|
||||||
|
|
||||||
filePath := setupDb()
|
|
||||||
db := database.NewDatabase(filePath, true, *debugFlag)
|
|
||||||
err := db.Open()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Str("Path", filePath).Msg("Could not open Database")
|
|
||||||
}
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
s := server.New(&provider.Bato{}, &db, mux, func(o *server.Options) {
|
|
||||||
authOptions := setupAuth()
|
|
||||||
o.Port = *portFlag
|
|
||||||
|
|
||||||
if *secretFlag != "" || *secretFilePathFlag != "" || *authFlag {
|
|
||||||
o.Auth.Set(authOptions)
|
|
||||||
}
|
|
||||||
interval, err := time.ParseDuration(*updateIntervalFlag)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Str("Interval", *updateIntervalFlag).Msg("Could not parse interval")
|
|
||||||
}
|
|
||||||
o.UpdateInterval = interval
|
|
||||||
|
|
||||||
if *certFlag != "" && *keyFlag != "" {
|
|
||||||
o.Tls.Apply(func(to *server.TlsOptions) {
|
|
||||||
to.CertPath = *certFlag
|
|
||||||
to.KeyPath = *keyFlag
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setupClient()
|
|
||||||
setupClose(&db)
|
|
||||||
err = s.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Could not start server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupAuth() server.AuthOptions {
|
|
||||||
var authOptions server.AuthOptions
|
|
||||||
if *secretFlag != "" {
|
|
||||||
authOptions.LoadType = server.Raw
|
|
||||||
authOptions.Secret = *secretFlag
|
|
||||||
} else if *secretFilePathFlag != "" {
|
|
||||||
authOptions.LoadType = server.File
|
|
||||||
authOptions.Secret = *secretFilePathFlag
|
|
||||||
} else if *authFlag {
|
|
||||||
path, err := getSecretPath()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Secret file could not be found")
|
|
||||||
}
|
|
||||||
authOptions.Secret = path
|
|
||||||
authOptions.LoadType = server.File
|
|
||||||
}
|
|
||||||
authOptions.MaxAge = *maxAgeFlag
|
|
||||||
authOptions.Secure = *secureFlag
|
|
||||||
return authOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupClient() {
|
|
||||||
if !*serverFlag {
|
|
||||||
go func() {
|
|
||||||
time.Sleep(300 * time.Millisecond)
|
|
||||||
err := open(fmt.Sprintf("http://localhost:%d", *portFlag))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not open Browser")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupClose(db *database.Manager) {
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(c, os.Interrupt)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for range c {
|
|
||||||
Close(db)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupDb() string {
|
|
||||||
if *databaseFlag != "" {
|
|
||||||
return *databaseFlag
|
|
||||||
} else {
|
|
||||||
return getDbPath()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupLogging() {
|
|
||||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
|
||||||
if *prettyLogsFlag {
|
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
|
||||||
}
|
|
||||||
if !*debugFlag {
|
|
||||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *logPathFlag != "" {
|
|
||||||
var console io.Writer = os.Stderr
|
|
||||||
if *prettyLogsFlag {
|
|
||||||
console = zerolog.ConsoleWriter{Out: os.Stderr}
|
|
||||||
}
|
|
||||||
log.Logger = log.Output(zerolog.MultiLevelWriter(console, &lumberjack.Logger{
|
|
||||||
Filename: *logPathFlag,
|
|
||||||
MaxAge: 14,
|
|
||||||
MaxBackups: 10,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
log.Debug().Msg("Closing Database")
|
|
||||||
err := db.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not close Database")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
64
release.go
64
release.go
@@ -1,64 +0,0 @@
|
|||||||
//go:build !Develop
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getSecretPath() (string, error) {
|
|
||||||
dir, err := os.UserCacheDir()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
dirPath := filepath.Join(dir, "MangaGetter")
|
|
||||||
filePath := filepath.Join(dirPath, "secret.secret")
|
|
||||||
return filePath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSecret() (string, error) {
|
|
||||||
dir, err := os.UserCacheDir()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
dirPath := filepath.Join(dir, "MangaGetter")
|
|
||||||
filePath := filepath.Join(dirPath, "secret.secret")
|
|
||||||
buf, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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