2 Commits

Author SHA1 Message Date
Pablu23
c1fa18fead WIP 2024-05-21 15:17:29 +02:00
Pablu23
20ad56b155 Initial test, but meh 2024-05-21 11:56:14 +02:00
26 changed files with 624 additions and 976 deletions

4
.gitignore vendored
View File

@@ -6,7 +6,3 @@ h.html
*.bak *.bak
/bin /bin
*.exe *.exe
*secret*
mangaGetter
*.crt
*.key

View File

@@ -1,5 +1,6 @@
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
release: release:

View File

@@ -2,13 +2,7 @@
package main package main
func getSecretPath() (string, error) { const port = 8080
return "", nil
}
func getSecret() (string, error) {
return "test", nil
}
func getDbPath() string { func getDbPath() string {
return "db.sqlite" return "db.sqlite"

9
go.mod
View File

@@ -7,18 +7,9 @@ require (
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
) )
require (
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 ( require (
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // 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/driver/sqlite v1.5.5
gorm.io/gorm v1.25.10 gorm.io/gorm v1.25.10
) )

23
go.sum
View File

@@ -1,34 +1,11 @@
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 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/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 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= 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 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=

View File

@@ -1,21 +1,24 @@
package database package database
type Chapter struct { type Chapter struct {
Id int `gorm:"primary_key;AUTO_INCREMENT"` Id int `gorm:"primary_key;autoIncrement;"`
ChapterId int
Url string Url string
Name string Name string
Number string Number string
TimeStampUnix int64 TimeStampUnix int64
MangaId int MangaId int
UserId int
} }
func NewChapter(id int, mangaId int, url string, name string, number string, timeStampUnix int64) Chapter { func NewChapter(id int, mangaId int, userId int, url string, name string, number string, timeStampUnix int64) Chapter {
return Chapter{ return Chapter{
Id: id, ChapterId: id,
Url: url, Url: url,
Name: name, Name: name,
Number: number, Number: number,
TimeStampUnix: timeStampUnix, TimeStampUnix: timeStampUnix,
MangaId: mangaId, MangaId: mangaId,
UserId: userId,
} }
} }

View File

@@ -2,46 +2,30 @@ package database
import ( import (
_ "embed" _ "embed"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger"
) )
type Manager struct { type Manager struct {
ConnectionString string ConnectionString string
Db *gorm.DB Db *gorm.DB
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, Db: nil,
CreateIfNotExists: createIfNotExists, CreateIfNotExists: createIfNotExists,
ActivateGormLogger: activateGormLogger,
} }
} }
func (dbMgr *Manager) Open() error { func (dbMgr *Manager) Open() error {
var db *gorm.DB db, err := gorm.Open(sqlite.Open(dbMgr.ConnectionString), &gorm.Config{})
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 {
db, err = gorm.Open(sqlite.Open(dbMgr.ConnectionString), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
return err
}
}
dbMgr.Db = db dbMgr.Db = db
if dbMgr.CreateIfNotExists { if dbMgr.CreateIfNotExists {
err = dbMgr.createDatabaseIfNotExists() err = dbMgr.createDatabaseIfNotExists()
@@ -70,6 +54,6 @@ func (dbMgr *Manager) Delete(mangaId int) {
} }
func (dbMgr *Manager) createDatabaseIfNotExists() error { func (dbMgr *Manager) createDatabaseIfNotExists() error {
err := dbMgr.Db.AutoMigrate(&Manga{}, &Chapter{}, &Setting{}) err := dbMgr.Db.AutoMigrate(&MangaDefinition{}, &User{}, &Manga{}, &Chapter{}, &Setting{})
return err return err
} }

View File

@@ -1,42 +1,58 @@
package database package database
type Manga struct { type MangaDefinition struct {
Id int `gorm:"primary_key;AUTO_INCREMENT"` Id int `gorm:"primary_key;AUTO_INCREMENT"`
Title string Title string
TimeStampUnix int64
Thumbnail []byte Thumbnail []byte
LastChapterNum string LastChapterNum string
Chapters []Chapter // Chapters []Chapter
Enabled bool
//`gorm:"foreignkey:MangaID"` //`gorm:"foreignkey:MangaID"`
} }
func NewManga(id int, title string, timeStampUnix int64) Manga { type Manga struct {
return Manga{ Id int `gorm:"primary_key;AUTO_INCREMENT"`
MangaDefinitionId int
Definition MangaDefinition `gorm:"foreignKey:MangaDefinitionId"`
UserId int
User User
TimeStampUnix int64
Chapters []Chapter `gorm:"foreignKey:MangaId"`
}
func NewMangaDefinition(id int, title string) MangaDefinition {
return MangaDefinition{
Id: id, Id: id,
Title: title, Title: title,
TimeStampUnix: timeStampUnix,
LastChapterNum: "", LastChapterNum: "",
Enabled: true, }
}
func NewManga(def MangaDefinition, user User, timeStampUnix int64) Manga {
return Manga{
MangaDefinitionId: def.Id,
Definition: def,
UserId: user.Id,
User: user,
TimeStampUnix: timeStampUnix,
} }
} }
// GetLatestChapter TODO: Cache this somehow // GetLatestChapter TODO: Cache this somehow
func (m *Manga) GetLatestChapter() (*Chapter, bool) { func (m *Manga) GetLatestChapter() (*Chapter, bool) {
highest := int64(0) // highest := int64(0)
index := 0 // index := 0
for i, chapter := range m.Chapters { // for i, chapter := range m.Chapters {
if chapter.MangaId == m.Id && highest < chapter.TimeStampUnix { // if chapter.MangaId == m.Manga.Id && highest < chapter.TimeStampUnix {
highest = chapter.TimeStampUnix // highest = chapter.TimeStampUnix
index = i // index = i
} // }
} // }
if highest == 0 { // if highest == 0 {
return nil, false return nil, false
} // }
return &m.Chapters[index], true // return &m.Chapters[index], true
//result := db.Where("manga.id = ?", m.Id).Order("TimeStampUnix desc").Take(&chapter) //result := db.Where("manga.id = ?", m.Id).Order("TimeStampUnix desc").Take(&chapter)
//if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { //if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {

17
internal/database/user.go Normal file
View File

@@ -0,0 +1,17 @@
package database
type User struct {
Id int `gorm:"primary_key;AUTO_INCREMENT"`
DisplayName string
LoginName string
PwdHash []byte
Salt []byte
}
// type UserManga struct {
// Id int `gorm:"primary_key;AUTO_INCREMENT"`
// DisplayName string
// Manga Manga
// User User
// // Chapters []Chapter `gorm:"ForeignKey:ChapterId,UserId;References:Id,UserId"`
// }

View File

@@ -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&quot;:\[1,&quot;\[(.*)]&quot;]`) reg, err := regexp.Compile(`<astro-island.*props=".*;imageFiles&quot;:\[1,&quot;\[(.*)]&quot;]`)
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
} }

View File

@@ -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)

View File

@@ -14,52 +14,70 @@ import (
"github.com/pablu23/mangaGetter/internal/database" "github.com/pablu23/mangaGetter/internal/database"
"github.com/pablu23/mangaGetter/internal/view" "github.com/pablu23/mangaGetter/internal/view"
"github.com/rs/zerolog/log"
"golang.org/x/text/cases" "golang.org/x/text/cases"
"golang.org/x/text/language" "golang.org/x/text/language"
"gorm.io/gorm" "gorm.io/gorm"
) )
func (s *Server) HandleDisable(w http.ResponseWriter, r *http.Request) { func (s *Server) getSessionFromCookie(w http.ResponseWriter, r *http.Request) (*UserSession, error) {
id := r.PostFormValue("mangaId") cookie, err := r.Cookie("session")
var manga database.Manga if err != nil {
s.DbMgr.Db.Where("id = ?", id).First(&manga) switch {
case errors.Is(err, http.ErrNoCookie):
if manga.Enabled { // http.Error(w, "cookie not found", http.StatusBadRequest)
http.Redirect(w, r, "/", http.StatusFound) http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
} else { default:
http.Redirect(w, r, "/archive", http.StatusFound) fmt.Println(err)
http.Error(w, "server error", http.StatusInternalServerError)
} }
}
manga.Enabled = !manga.Enabled session, ok := s.Sessions[cookie.Value]
s.DbMgr.Db.Save(&manga) if !ok {
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
return nil, errors.New("Unknown Session")
}
return session, err
} }
func (s *Server) HandleUpdate(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleRegister(w http.ResponseWriter, r *http.Request) {
s.UpdateMangaList() s.Mutex.Lock()
http.Redirect(w, r, "/", http.StatusFound) defer s.Mutex.Unlock()
}
func (s *Server) HandleLoginPost(w http.ResponseWriter, r *http.Request) { admin := database.User{
if s.options.Auth.Enabled { Id: 1,
auth := s.options.Auth.Get() DisplayName: "admin",
secret := r.PostFormValue("secret") LoginName: "admin",
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) s.DbMgr.Db.Create(&admin)
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
} }
func (s *Server) HandleLogin(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleLogin(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(view.GetViewTemplate(view.Login)) s.Mutex.Lock()
tmpl.Execute(w, nil) defer s.Mutex.Unlock()
// Login
s.Sessions["abcd"] = &UserSession{
User: database.User{
Id: 1,
DisplayName: "admin",
LoginName: "admin",
},
}
cookie := http.Cookie{
Name: "session",
Value: "abcd",
Path: "/",
MaxAge: 3600,
Secure: true,
HttpOnly: false,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, &cookie)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
} }
func (s *Server) HandleNew(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleNew(w http.ResponseWriter, r *http.Request) {
@@ -68,55 +86,52 @@ func (s *Server) HandleNew(w http.ResponseWriter, r *http.Request) {
url := fmt.Sprintf("/title/%s/%s", title, chapter) url := fmt.Sprintf("/title/%s/%s", title, chapter)
s.CurrSubUrl = url session, err := s.getSessionFromCookie(w, r)
s.PrevSubUrl = "" if err != nil {
s.NextSubUrl = "" return
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) session.CurrSubUrl = url
session.PrevSubUrl = ""
session.NextSubUrl = ""
s.LoadCurr(session)
go s.LoadNext(session)
go s.LoadPrev(session)
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
} }
func (s *Server) HandleMenu(w http.ResponseWriter, _ *http.Request) { func (s *Server) HandleMenu(w http.ResponseWriter, r *http.Request) {
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) session, err := s.getSessionFromCookie(w, r)
if err != nil {
return
}
var all []*database.Manga
_ = s.DbMgr.Db.Preload("Chapters").Where("user_id = ?", session.User.Id).Find(&all)
l := len(all)
mangaViewModels := make([]view.MangaViewModel, l) mangaViewModels := make([]view.MangaViewModel, l)
counter := 0 counter := 0
n := time.Now().UnixNano()
var tmp []database.Setting
s.DbMgr.Db.Find(&tmp)
settings := make(map[string]database.Setting)
for _, m := range tmp {
settings[m.Name] = m
}
var thumbNs int64 = 0
var titNs int64 = 0
//TODO: Change all this to be more performant //TODO: Change all this to be more performant
for _, manga := range mangas { for _, manga := range all {
title := cases.Title(language.English, cases.Compact).String(strings.Replace(manga.Title, "-", " ", -1)) title := cases.Title(language.English, cases.Compact).String(strings.Replace(manga.Definition.Title, "-", " ", -1))
t1 := time.Now().UnixNano()
thumbnail, updated, err := s.LoadThumbnail(manga) thumbnail, updated, err := s.LoadThumbnail(manga)
//TODO: Add default picture instead of not showing Manga at all //TODO: Add default picture instead of not showing Manga at all
@@ -126,37 +141,54 @@ func (s *Server) ViewMenu(w http.ResponseWriter, mangas []*database.Manga, setti
if updated { if updated {
s.DbMgr.Db.Save(manga) s.DbMgr.Db.Save(manga)
} }
t2 := time.Now().UnixNano()
thumbNs += t2 - t1
t1 = time.Now().UnixNano()
// This is very slow // This is very slow
// TODO: put this into own Method // TODO: put this into own Method
if manga.LastChapterNum == "" { if manga.Definition.LastChapterNum == "" {
err, updated := s.UpdateLatestAvailableChapter(manga) err, updated := s.UpdateLatestAvailableChapter(manga)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Could not update latest available chapters") fmt.Println(err)
} }
if updated { if updated {
s.DbMgr.Db.Save(manga) s.DbMgr.Db.Save(manga.Definition)
} }
} }
t2 = time.Now().UnixNano()
titNs += t2 - t1
latestChapter, ok := manga.GetLatestChapter() latestChapter, ok := manga.GetLatestChapter()
if !ok { if !ok {
continue continue
} }
mangaViewModels[counter] = view.MangaViewModel{ mangaViewModels[counter] = view.MangaViewModel{
ID: manga.Id, ID: manga.Definition.Id,
Title: title, Title: title,
Number: latestChapter.Number, Number: latestChapter.Number,
LastNumber: manga.LastChapterNum, LastNumber: manga.Definition.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: latestChapter.Url,
ThumbnailUrl: thumbnail, ThumbnailUrl: thumbnail,
Enabled: manga.Enabled,
} }
counter++ counter++
} }
fmt.Printf("Loading Thumbnails took %d ms\n", (thumbNs)/1000000)
fmt.Printf("Loading latest Chapters took %d ms\n", (titNs)/1000000)
nex := time.Now().UnixNano()
fmt.Printf("Creating Viewmodels took %d ms\n", (nex-n)/1000000)
n = time.Now().UnixNano()
order, ok := settings["order"] order, ok := settings["order"]
if !ok || order.Value == "title" { if !ok || order.Value == "title" {
slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int { slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int {
@@ -180,15 +212,17 @@ func (s *Server) ViewMenu(w http.ResponseWriter, mangas []*database.Manga, setti
}) })
} }
nex = time.Now().UnixNano()
fmt.Printf("Sorting took %d ms\n", (nex-n)/1000000)
menuViewModel := view.MenuViewModel{ menuViewModel := view.MenuViewModel{
Settings: settings, 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,104 +230,116 @@ 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) s.DbMgr.Delete(mangaId)
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) http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
// session, err := s.getSessionFromCookie(w, r)
// if err != nil {
// return
// }
go func() { go func() {
s.Mutex.Lock() // session.Mutex.Lock()
if s.PrevViewModel != nil { // if session.PrevViewModel != nil {
for _, img := range s.PrevViewModel.Images { // for _, img := range session.PrevViewModel.Images {
delete(s.ImageBuffers, img.Path) // delete(s.ImageBuffers, img.Path)
} // }
} // }
if s.CurrViewModel != nil { // if session.CurrViewModel != nil {
//
for _, img := range s.CurrViewModel.Images { // for _, img := range session.CurrViewModel.Images {
delete(s.ImageBuffers, img.Path) // delete(s.ImageBuffers, img.Path)
} // }
} // }
if s.NextViewModel != nil { // if session.NextViewModel != nil {
//
for _, img := range s.NextViewModel.Images { // for _, img := range session.NextViewModel.Images {
delete(s.ImageBuffers, img.Path) // delete(s.ImageBuffers, img.Path)
} // }
} // }
s.Mutex.Unlock() // session.Mutex.Unlock()
log.Info().Msg("Cleaned up images") fmt.Println("Cleaned last Manga")
}() }()
} }
func (s *Server) HandleCurrent(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleCurrent(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(view.GetViewTemplate(view.Viewer)) tmpl := template.Must(view.GetViewTemplate(view.Viewer))
mangaId, chapterId, err := s.Provider.GetTitleIdAndChapterId(s.CurrSubUrl) session, err := s.getSessionFromCookie(w, r)
if err != nil { if err != nil {
log.Error().Err(err).Str("subUrl", s.CurrSubUrl).Msg("Could not get TitleId and ChapterId")
http.Redirect(w, r, "/", http.StatusFound)
return return
} }
mangaId, chapterId, err := s.Provider.GetTitleIdAndChapterId(session.CurrSubUrl)
title, chapterName, err := s.Provider.GetTitleAndChapter(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)
}
title, chapterName, err := s.Provider.GetTitleAndChapter(session.CurrSubUrl)
if err != nil {
fmt.Println(err)
}
var mangaDef database.MangaDefinition
result := s.DbMgr.Db.First(&mangaDef, mangaId)
if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) {
mangaDef = database.NewMangaDefinition(mangaId, title)
} }
var manga database.Manga var manga database.Manga
result := s.DbMgr.Db.First(&manga, mangaId) result = s.DbMgr.Db.Where("user_id = ?", session.User.Id).First(&manga, "manga_definition_id = ?", mangaId)
if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) { if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) {
manga = database.NewManga(mangaId, title, time.Now().Unix()) manga = database.NewManga(mangaDef, session.User, time.Now().Unix())
} else {
manga.TimeStampUnix = time.Now().Unix()
} }
var chapter database.Chapter var chapter database.Chapter
result = s.DbMgr.Db.First(&chapter, chapterId) result = s.DbMgr.Db.Where("user_id = ?", session.User.Id).First(&chapter, "chapter_id = ?", chapterId)
if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) { if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) {
chapterNumberStr := strings.Replace(chapterName, "ch_", "", 1) chapterNumberStr := strings.Replace(chapterName, "ch_", "", 1)
chapter = database.NewChapter(chapterId, mangaId, s.CurrSubUrl, chapterName, chapterNumberStr, time.Now().Unix()) chapter = database.NewChapter(chapterId, mangaId, session.User.Id, session.CurrSubUrl, chapterName, chapterNumberStr, time.Now().Unix())
} else { } else {
chapter.TimeStampUnix = time.Now().Unix() chapter.TimeStampUnix = time.Now().Unix()
} }
s.DbMgr.Db.Save(&mangaDef)
s.DbMgr.Db.Save(&manga) s.DbMgr.Db.Save(&manga)
s.DbMgr.Db.Save(&chapter) s.DbMgr.Db.Save(&chapter)
err = tmpl.Execute(w, s.CurrViewModel) err = tmpl.Execute(w, session.CurrViewModel)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Could not template Current") fmt.Println(err)
} }
} }
func (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) {
u := r.PathValue("url") u := r.PathValue("url")
s.Mutex.Lock() s.Mutex.RLock()
defer s.Mutex.Unlock() defer s.Mutex.RUnlock()
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)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Could not write image") fmt.Println(err)
} }
} }
@@ -304,62 +350,74 @@ func (s *Server) HandleFavicon(w http.ResponseWriter, _ *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) {
if s.PrevViewModel != nil { fmt.Println("Received Next")
session, err := s.getSessionFromCookie(w, r)
if err != nil {
return
}
if session.PrevViewModel != nil {
go func(viewModel view.ImageViewModel, s *Server) { go func(viewModel view.ImageViewModel, s *Server) {
s.Mutex.Lock() s.Mutex.Lock()
for _, img := range viewModel.Images { for _, img := range viewModel.Images {
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) }(*session.PrevViewModel, s)
} }
if s.NextViewModel == nil || s.NextSubUrl == "" { if session.NextViewModel == nil || session.NextSubUrl == "" {
http.Redirect(w, r, "/", http.StatusFound) http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return return
} }
s.PrevViewModel = s.CurrViewModel session.PrevViewModel = session.CurrViewModel
s.CurrViewModel = s.NextViewModel session.CurrViewModel = session.NextViewModel
s.PrevSubUrl = s.CurrSubUrl session.PrevSubUrl = session.CurrSubUrl
s.CurrSubUrl = s.NextSubUrl session.CurrSubUrl = session.NextSubUrl
go s.LoadNext() go s.LoadNext(session)
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) {
if s.NextViewModel != nil { fmt.Println("Received Prev")
session, err := s.getSessionFromCookie(w, r)
if err != nil {
return
}
if session.NextViewModel != nil {
go func(viewModel view.ImageViewModel, s *Server) { go func(viewModel view.ImageViewModel, s *Server) {
s.Mutex.Lock() s.Mutex.Lock()
for _, img := range viewModel.Images { for _, img := range viewModel.Images {
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) }(*session.NextViewModel, s)
} }
if s.PrevViewModel == nil || s.PrevSubUrl == "" { if session.PrevViewModel == nil || session.PrevSubUrl == "" {
http.Redirect(w, r, "/", http.StatusFound) http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return return
} }
s.NextViewModel = s.CurrViewModel session.NextViewModel = session.CurrViewModel
s.CurrViewModel = s.PrevViewModel session.CurrViewModel = session.PrevViewModel
s.NextSubUrl = s.CurrSubUrl session.NextSubUrl = session.CurrSubUrl
s.CurrSubUrl = s.PrevSubUrl session.CurrSubUrl = session.PrevSubUrl
go s.LoadPrev() go s.LoadPrev(session)
http.Redirect(w, r, "/current/", http.StatusFound) http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
} }
func (s *Server) HandleSettingSet(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleSettingSet(w http.ResponseWriter, r *http.Request) {
@@ -376,7 +434,7 @@ func (s *Server) HandleSettingSet(w http.ResponseWriter, r *http.Request) {
s.DbMgr.Db.Model(&setting).Update("value", settingValue) s.DbMgr.Db.Model(&setting).Update("value", settingValue)
} }
http.Redirect(w, r, "/", http.StatusFound) http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
} }
func (s *Server) HandleSetting(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleSetting(w http.ResponseWriter, r *http.Request) {
@@ -389,28 +447,29 @@ func (s *Server) HandleSetting(w http.ResponseWriter, r *http.Request) {
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
set := database.NewSetting(settingName, settingValue) set := database.NewSetting(settingName, settingValue)
s.DbMgr.Db.Save(&set) s.DbMgr.Db.Save(&set)
} else if res.Error != nil {
log.Error().Err(res.Error).Send()
} else { } else {
s.DbMgr.Db.Model(&setting).Update("value", settingValue) s.DbMgr.Db.Model(&setting).Update("value", settingValue)
} }
http.Redirect(w, r, "/", http.StatusFound) http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
} }
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)
url := fmt.Sprintf("/title/%s", sub) url := fmt.Sprintf("/title/%s", sub)
s.CurrSubUrl = url session, err := s.getSessionFromCookie(w, r)
s.PrevSubUrl = "" if err != nil {
s.NextSubUrl = "" return
s.LoadCurr() }
session.CurrSubUrl = url
session.PrevSubUrl = ""
session.NextSubUrl = ""
s.LoadCurr(session)
go s.LoadNext() go s.LoadNext(session)
go s.LoadPrev() go s.LoadPrev(session)
http.Redirect(w, r, "/current/", http.StatusFound) http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
} }

View File

@@ -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)
}
})
}

View File

@@ -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,
}
}

View File

@@ -2,284 +2,218 @@ package server
import ( import (
"bytes" "bytes"
"crypto/tls"
_ "embed" _ "embed"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/pablu23/mangaGetter/internal/database" "github.com/pablu23/mangaGetter/internal/database"
"github.com/pablu23/mangaGetter/internal/provider" "github.com/pablu23/mangaGetter/internal/provider"
"github.com/pablu23/mangaGetter/internal/view" "github.com/pablu23/mangaGetter/internal/view"
"github.com/rs/zerolog/log"
) )
type Server struct { type Server struct {
ImageBuffers map[string][]byte
Provider provider.Provider
DbMgr *database.Manager
Mutex *sync.RWMutex
Sessions map[string]*UserSession
}
type UserSession struct {
User database.User
// Mutex *sync.Mutex
PrevSubUrl string
CurrSubUrl string
NextSubUrl string
PrevViewModel *view.ImageViewModel PrevViewModel *view.ImageViewModel
CurrViewModel *view.ImageViewModel CurrViewModel *view.ImageViewModel
NextViewModel *view.ImageViewModel NextViewModel *view.ImageViewModel
ImageBuffers map[string][]byte
Mutex *sync.Mutex
NextSubUrl string
CurrSubUrl string
PrevSubUrl string
Provider provider.Provider
IsFirst bool
IsLast bool
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 { func New(provider provider.Provider, db *database.Manager) *Server {
opts := NewDefaultOptions()
for _, opt := range options {
opt(&opts)
}
s := Server{ s := Server{
ImageBuffers: make(map[string][]byte), ImageBuffers: make(map[string][]byte),
Sessions: make(map[string]*UserSession),
Provider: provider, Provider: provider,
DbMgr: db, DbMgr: db,
Mutex: &sync.Mutex{}, Mutex: &sync.RWMutex{},
mux: mux,
options: opts,
} }
return &s return &s
} }
func (s *Server) RegisterRoutes() { func (s *Server) Start(port int) error {
s.mux.HandleFunc("GET /login", s.HandleLogin) http.HandleFunc("/register", s.HandleRegister)
s.mux.HandleFunc("POST /login", s.HandleLoginPost) http.HandleFunc("/login", s.HandleLogin)
s.mux.HandleFunc("/", s.HandleMenu) http.HandleFunc("/", s.HandleMenu)
s.mux.HandleFunc("/new/", s.HandleNewQuery) http.HandleFunc("/new/", s.HandleNewQuery)
s.mux.HandleFunc("/new/title/{title}/{chapter}", s.HandleNew) http.HandleFunc("/new/title/{title}/{chapter}", s.HandleNew)
s.mux.HandleFunc("/current/", s.HandleCurrent) http.HandleFunc("/current/", s.HandleCurrent)
s.mux.HandleFunc("/img/{url}", s.HandleImage) http.HandleFunc("/img/{url}/", s.HandleImage)
s.mux.HandleFunc("POST /next", s.HandleNext) http.HandleFunc("POST /next", s.HandleNext)
s.mux.HandleFunc("POST /prev", s.HandlePrev) http.HandleFunc("POST /prev", s.HandlePrev)
s.mux.HandleFunc("POST /exit", s.HandleExit) http.HandleFunc("POST /exit", s.HandleExit)
s.mux.HandleFunc("POST /delete", s.HandleDelete) http.HandleFunc("POST /delete", s.HandleDelete)
s.mux.HandleFunc("/favicon.ico", s.HandleFavicon) http.HandleFunc("/favicon.ico", s.HandleFavicon)
s.mux.HandleFunc("POST /setting/", s.HandleSetting) http.HandleFunc("POST /setting/", s.HandleSetting)
s.mux.HandleFunc("GET /setting/set/{setting}/{value}", s.HandleSettingSet) http.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 { // Update Latest Chapters every 5 Minutes
server := http.Server{ // go func(s *Server) {
Addr: fmt.Sprintf(":%d", s.options.Port), // time.AfterFunc(time.Second*10, func() {
Handler: s.mux, // var all []*database.Manga
} // s.DbMgr.Db.Find(&all)
s.RegisterRoutes() // for _, m := range all {
s.registerUpdater() // err, updated := s.UpdateLatestAvailableChapter(m)
// if err != nil {
if s.options.Auth.Enabled { // fmt.Println(err)
auth := s.options.Auth.Get() // }
switch auth.LoadType { // if updated {
case Raw: // s.DbMgr.Db.Save(m)
s.secret = auth.Secret // }
case File: // }
secretBytes, err := os.ReadFile(auth.Secret) // })
if err != nil { //
// for {
// select {
// case <-time.After(time.Minute * 5):
// var all []*database.Manga
// s.DbMgr.Db.Find(&all)
// for _, m := range all {
// err, updated := s.UpdateLatestAvailableChapter(m)
// if err != nil {
// fmt.Println(err)
// }
// if updated {
// s.DbMgr.Db.Save(m)
// }
// }
// }
// }
// }(s)
//
fmt.Println("Server starting...")
err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
return err 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() { func (s *Server) LoadNext(session *UserSession) {
var all []*database.Manga next, err := s.Provider.GetHtml(session.CurrSubUrl)
s.DbMgr.Db.Where("enabled = 1").Find(&all)
for _, m := range all {
err, updated := s.UpdateLatestAvailableChapter(m)
if err != nil { if err != nil {
log.Error().Err(err).Str("Manga", m.Title).Msg("Could not update latest available chapters") fmt.Println(err)
} session.NextSubUrl = ""
if updated { session.NextViewModel = nil
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() {
c, err := s.Provider.GetHtml(s.CurrSubUrl)
if err != nil {
log.Error().Err(err).Msg("Could not get Html for current chapter")
s.NextSubUrl = ""
s.NextViewModel = nil
return
}
next, err := s.Provider.GetNext(c)
if err != nil {
log.Error().Err(err).Msg("Could not load next chapter")
s.NextSubUrl = ""
s.NextViewModel = nil
return return
} }
html, err := s.Provider.GetHtml(next) html, err := s.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 = "" session.NextSubUrl = ""
s.NextViewModel = nil session.NextViewModel = nil
return return
} }
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 = "" session.NextSubUrl = ""
s.NextViewModel = nil session.NextViewModel = nil
return return
} }
title, chapter, err := s.Provider.GetTitleAndChapter(next) title, chapter, err := s.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_?"
} }
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1) full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
s.NextViewModel = &view.ImageViewModel{Images: imagesNext, Title: full} session.NextViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
s.NextSubUrl = next session.NextSubUrl = next
log.Debug().Msg("Successfully loaded next chapter") fmt.Println("Loaded next")
} }
func (s *Server) LoadPrev() { func (s *Server) LoadPrev(session *UserSession) {
c, err := s.Provider.GetHtml(s.CurrSubUrl) c, err := s.Provider.GetHtml(session.CurrSubUrl)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Could not get Html for current chapter") fmt.Println(err)
s.PrevSubUrl = "" session.PrevSubUrl = ""
s.PrevViewModel = nil session.PrevViewModel = nil
return return
} }
prev, err := s.Provider.GetPrev(c) prev, err := s.Provider.GetPrev(c)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Could not load prev chapter") fmt.Println(err)
s.PrevSubUrl = "" session.PrevSubUrl = ""
s.PrevViewModel = nil session.PrevViewModel = nil
return return
} }
html, err := s.Provider.GetHtml(prev) html, err := s.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 = "" session.PrevSubUrl = ""
s.PrevViewModel = nil session.PrevViewModel = nil
return return
} }
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 = "" session.PrevSubUrl = ""
s.PrevViewModel = nil session.PrevViewModel = nil
return return
} }
title, chapter, err := s.Provider.GetTitleAndChapter(prev) title, chapter, err := s.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_?"
} }
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1) full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
s.PrevViewModel = &view.ImageViewModel{Images: imagesNext, Title: full} session.PrevViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
s.PrevSubUrl = prev
log.Debug().Msg("Successfully loaded prev chapter")
session.PrevSubUrl = prev
fmt.Println("Loaded prev")
} }
func (s *Server) LoadCurr() { func (s *Server) LoadCurr(session *UserSession) {
html, err := s.Provider.GetHtml(s.CurrSubUrl) html, err := s.Provider.GetHtml(session.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.Provider.GetTitleAndChapter(session.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_?"
} }
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} session.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) UpdateLatestAvailableChapter(manga *database.Manga) (error, bool) {
log.Info().Str("Manga", manga.Title).Msg("Updating Manga") fmt.Printf("Updating Manga: %s\n", manga.Definition.Title)
l, err := s.Provider.GetChapterList("/title/" + strconv.Itoa(manga.Id)) l, err := s.Provider.GetChapterList("/title/" + strconv.Itoa(manga.Definition.Id))
if err != nil { if err != nil {
return err, false return err, false
} }
@@ -292,16 +226,16 @@ func (s *Server) UpdateLatestAvailableChapter(manga *database.Manga) (error, boo
chapterNumberStr := strings.Replace(c, "ch_", "", 1) chapterNumberStr := strings.Replace(c, "ch_", "", 1)
if manga.LastChapterNum == chapterNumberStr { if manga.Definition.LastChapterNum == chapterNumberStr {
return nil, false return nil, false
} else { } else {
manga.LastChapterNum = chapterNumberStr manga.Definition.LastChapterNum = chapterNumberStr
return nil, true return nil, true
} }
} }
func (s *Server) LoadThumbnail(manga *database.Manga) (path string, updated bool, err error) { func (s *Server) LoadThumbnail(manga *database.Manga) (path string, updated bool, err error) {
strId := strconv.Itoa(manga.Id) strId := strconv.Itoa(manga.Definition.Id)
s.Mutex.Lock() s.Mutex.Lock()
defer s.Mutex.Unlock() defer s.Mutex.Unlock()
@@ -309,8 +243,8 @@ func (s *Server) LoadThumbnail(manga *database.Manga) (path string, updated bool
return strId, false, nil return strId, false, nil
} }
if manga.Thumbnail != nil { if manga.Definition.Thumbnail != nil {
s.ImageBuffers[strId] = manga.Thumbnail s.ImageBuffers[strId] = manga.Definition.Thumbnail
return strId, false, nil return strId, false, nil
} }
@@ -322,7 +256,7 @@ func (s *Server) LoadThumbnail(manga *database.Manga) (path string, updated bool
if err != nil { if err != nil {
return "", false, err return "", false, err
} }
manga.Thumbnail = ram manga.Definition.Thumbnail = ram
s.ImageBuffers[strId] = ram s.ImageBuffers[strId] = ram
return strId, true, nil return strId, true, nil
} }
@@ -365,7 +299,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)

View File

@@ -0,0 +1,31 @@
package utils
import "sync"
type ConcurrentMap[K comparable, Value any] struct {
dirty map[K]Value
count int
mutex *sync.RWMutex
}
func NewConcurrentMap[K comparable, V any]() ConcurrentMap[K, V] {
return ConcurrentMap[K, V]{
dirty: make(map[K]V),
count: 0,
mutex: &sync.RWMutex{},
}
}
func (c *ConcurrentMap[K, Value]) Get(key K) (Value, bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()
val, ok := c.dirty[key]
return val, ok
}
func (c *ConcurrentMap[K, Value]) Set(key K, val Value) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.dirty[key] = val
}

View File

@@ -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>

View File

@@ -1,10 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<!--suppress CssUnusedSymbol --> <!--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 {
@@ -32,7 +31,7 @@
color: #FFFFFF; color: #FFFFFF;
cursor: pointer; cursor: pointer;
flex-shrink: 0; flex-shrink: 0;
font-family: "Inter UI", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; font-family: "Inter UI","SF Pro Display",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
height: 4rem; height: 4rem;
@@ -54,13 +53,12 @@
.button-36 { .button-36 {
padding: 0 2.6rem; padding: 0 2.6rem;
} }
.button-delete{
.button-delete {
padding: 0 2.6rem; padding: 0 2.6rem;
} }
} }
.button-delete { .button-delete{
background-image: linear-gradient(92.88deg, #f44336 9.16%, #f44336 43.89%, #f44336 64.72%); background-image: linear-gradient(92.88deg, #f44336 9.16%, #f44336 43.89%, #f44336 64.72%);
border-radius: 8px; border-radius: 8px;
border-style: none; border-style: none;
@@ -68,7 +66,7 @@
color: #FFFFFF; color: #FFFFFF;
cursor: pointer; cursor: pointer;
flex-shrink: 0; flex-shrink: 0;
font-family: "Inter UI", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; font-family: "Inter UI","SF Pro Display",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
height: 4rem; height: 4rem;
@@ -86,22 +84,18 @@
transition-duration: .1s; transition-duration: .1s;
} }
.table-left { .table-left{
text-align: left; text-align: left;
} }
.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{
box-shadow: 0 0 2px 1px rgba(0, 140, 186, 0.5); box-shadow: 0 0 2px 1px rgba(0, 140, 186, 0.5);
} }
@@ -109,7 +103,7 @@
width: 100%; width: 100%;
} }
td { td{
text-align: center; text-align: center;
} }
@@ -125,10 +119,10 @@
margin-bottom: 10px; margin-bottom: 10px;
margin-top: 10px; margin-top: 10px;
} }
</style> </style>
</head> </head>
<body class="{{(index .Settings "theme").Value}}">
<body class='{{(index .Settings "theme").Value}}'>
<form method="post" action="/new/"> <form method="post" action="/new/">
<label> <label>
New Sub Url New Sub Url
@@ -137,29 +131,11 @@
<input type="submit" value="Open" class="button-36"> <input type="submit" value="Open" class="button-36">
</form> </form>
<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/"> <form method="post" action="/setting/">
<label for="theme">Theme</label> <label for="theme">Theme</label>
<select onchange="this.form.submit()" id="theme" name="theme"> <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 "white"}} selected {{end}} value="white">White</option>
<option {{if eq (index .Settings "theme" ).Value "dark" }} selected {{end}} value="dark">Dark</option> <option {{if eq (index .Settings "theme").Value "dark"}} selected {{end}} value="dark">Dark</option>
</select> </select>
<input type="hidden" name="setting" value="theme"> <input type="hidden" name="setting" value="theme">
</form> </form>
@@ -171,14 +147,13 @@
<th><a href="setting/set/order/chapter">Current Chapter</a></th> <th><a href="setting/set/order/chapter">Current Chapter</a></th>
<th><a href="setting/set/order/last">Last Accessed</a></th> <th><a href="setting/set/order/last">Last Accessed</a></th>
<th>Link</th> <th>Link</th>
<th>Disable/Enable</th>
<th>Delete</th> <th>Delete</th>
</tr> </tr>
{{range .Mangas}} {{range .Mangas}}
<tr> <tr>
<td> <td>
<a target="_blank" href="/img/{{.ThumbnailUrl}}"> <a target="_blank" href="/img/{{.ThumbnailUrl}}">
<img class="thumbnail" src="/img/{{.ThumbnailUrl}}" alt="img_{{.ThumbnailUrl}}" /> <img class="thumbnail" src="/img/{{.ThumbnailUrl}}" alt="img_{{.ThumbnailUrl}}"/>
</a> </a>
</td> </td>
<td class="table-left">{{.Title}}</td> <td class="table-left">{{.Title}}</td>
@@ -191,12 +166,6 @@
</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 +176,4 @@
{{end}} {{end}}
</table> </table>
</body> </body>
</html> </html>

View File

@@ -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")
} }

View File

@@ -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)
} }

View File

@@ -20,11 +20,9 @@ type MangaViewModel struct {
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 Settings map[string]database.Setting
Mangas []MangaViewModel Mangas []MangaViewModel
} }

View File

@@ -5,5 +5,4 @@ type View int
const ( const (
Menu View = iota Menu View = iota
Viewer View = iota Viewer View = iota
Login View = iota
) )

146
main.go
View File

@@ -1,154 +1,49 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"io" "github.com/pablu23/mangaGetter/internal/database"
"net/http" "github.com/pablu23/mangaGetter/internal/provider"
"github.com/pablu23/mangaGetter/internal/server"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"runtime" "runtime"
"time" "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() { func main() {
flag.Parse() filePath := getDbPath()
setupLogging() db := database.NewDatabase(filePath, true)
filePath := setupDb()
db := database.NewDatabase(filePath, true, *debugFlag)
err := db.Open() err := db.Open()
if err != nil { if err != nil {
log.Fatal().Err(err).Str("Path", filePath).Msg("Could not open Database") fmt.Println(err)
return
} }
mux := http.NewServeMux() s := server.New(&provider.Bato{}, &db)
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) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt) signal.Notify(c, os.Interrupt)
go func() { go func() {
for range c { for range c {
Close(db) Close(&db)
} }
}() }()
}
func setupDb() string { go func() {
if *databaseFlag != "" { time.Sleep(300 * time.Millisecond)
return *databaseFlag err := open(fmt.Sprintf("http://localhost:%d", port))
} else { if err != nil {
return getDbPath() fmt.Println(err)
} }
} }()
func setupLogging() { err = s.Start(port)
zerolog.SetGlobalLevel(zerolog.InfoLevel) if err != nil {
if *prettyLogsFlag { panic(err)
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,
}))
} }
} }
@@ -170,11 +65,10 @@ func open(url string) error {
} }
func Close(db *database.Manager) { func Close(db *database.Manager) {
log.Debug().Msg("Closing Database") fmt.Println("Attempting to save and close DB")
err := db.Close() err := db.Close()
if err != nil { if err != nil {
log.Error().Err(err).Msg("Could not close Database") fmt.Println(err)
return
} }
os.Exit(0) os.Exit(0)
} }

BIN
mangaGetter Executable file

Binary file not shown.

View File

@@ -7,31 +7,7 @@ import (
"path/filepath" "path/filepath"
) )
func getSecretPath() (string, error) { const port = 8000
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 { func getDbPath() string {
dir, err := os.UserCacheDir() dir, err := os.UserCacheDir()

View File

@@ -1,2 +0,0 @@
{"level":"info","Port":8080,"time":"2024-05-31T00:22:54+02:00","message":"Starting server"}
{"level":"debug","time":"2024-05-31T00:22:58+02:00","message":"Closing Database"}