476 lines
12 KiB
Go
476 lines
12 KiB
Go
package server
|
|
|
|
import (
|
|
"cmp"
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pablu23/mangaGetter/internal/database"
|
|
"github.com/pablu23/mangaGetter/internal/view"
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func (s *Server) getSessionFromCookie(w http.ResponseWriter, r *http.Request) (*UserSession, error) {
|
|
cookie, err := r.Cookie("session")
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, http.ErrNoCookie):
|
|
// http.Error(w, "cookie not found", http.StatusBadRequest)
|
|
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
|
default:
|
|
fmt.Println(err)
|
|
http.Error(w, "server error", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
session, ok := s.Sessions[cookie.Value]
|
|
if !ok {
|
|
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
|
return nil, errors.New("Unknown Session")
|
|
}
|
|
return session, err
|
|
}
|
|
|
|
func (s *Server) HandleRegister(w http.ResponseWriter, r *http.Request) {
|
|
s.Mutex.Lock()
|
|
defer s.Mutex.Unlock()
|
|
|
|
admin := database.User{
|
|
Id: 1,
|
|
DisplayName: "admin",
|
|
LoginName: "admin",
|
|
}
|
|
s.DbMgr.Db.Create(&admin)
|
|
|
|
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
|
}
|
|
|
|
func (s *Server) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
|
s.Mutex.Lock()
|
|
defer s.Mutex.Unlock()
|
|
|
|
// Login
|
|
s.Sessions["abcd"] = &UserSession{
|
|
User: database.User{
|
|
Id: 1,
|
|
DisplayName: "admin",
|
|
LoginName: "admin",
|
|
},
|
|
}
|
|
|
|
cookie := http.Cookie{
|
|
Name: "session",
|
|
Value: "abcd",
|
|
Path: "/",
|
|
MaxAge: 3600,
|
|
Secure: true,
|
|
HttpOnly: false,
|
|
SameSite: http.SameSiteLaxMode,
|
|
}
|
|
|
|
http.SetCookie(w, &cookie)
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
}
|
|
|
|
func (s *Server) HandleNew(w http.ResponseWriter, r *http.Request) {
|
|
title := r.PathValue("title")
|
|
chapter := r.PathValue("chapter")
|
|
|
|
url := fmt.Sprintf("/title/%s/%s", title, chapter)
|
|
|
|
session, err := s.getSessionFromCookie(w, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
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, r *http.Request) {
|
|
tmpl := template.Must(view.GetViewTemplate(view.Menu))
|
|
|
|
session, err := s.getSessionFromCookie(w, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var all []*database.Manga
|
|
_ = s.DbMgr.Db.Preload("Chapters").Where("user_id = ?", session.User.Id).Find(&all)
|
|
l := len(all)
|
|
mangaViewModels := make([]view.MangaViewModel, l)
|
|
counter := 0
|
|
|
|
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
|
|
for _, manga := range all {
|
|
title := cases.Title(language.English, cases.Compact).String(strings.Replace(manga.Definition.Title, "-", " ", -1))
|
|
|
|
t1 := time.Now().UnixNano()
|
|
|
|
thumbnail, updated, err := s.LoadThumbnail(manga)
|
|
//TODO: Add default picture instead of not showing Manga at all
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if updated {
|
|
s.DbMgr.Db.Save(manga)
|
|
}
|
|
|
|
t2 := time.Now().UnixNano()
|
|
|
|
thumbNs += t2 - t1
|
|
|
|
t1 = time.Now().UnixNano()
|
|
|
|
// This is very slow
|
|
// TODO: put this into own Method
|
|
if manga.Definition.LastChapterNum == "" {
|
|
err, updated := s.UpdateLatestAvailableChapter(manga)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
if updated {
|
|
s.DbMgr.Db.Save(manga.Definition)
|
|
}
|
|
}
|
|
|
|
t2 = time.Now().UnixNano()
|
|
|
|
titNs += t2 - t1
|
|
latestChapter, ok := manga.GetLatestChapter()
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
mangaViewModels[counter] = view.MangaViewModel{
|
|
ID: manga.Definition.Id,
|
|
Title: title,
|
|
Number: latestChapter.Number,
|
|
LastNumber: manga.Definition.LastChapterNum,
|
|
// I Hate this time Format... 15 = hh, 04 = mm, 02 = DD, 01 = MM, 06 == YY
|
|
LastTime: time.Unix(manga.TimeStampUnix, 0).Format("15:04 (02-01-06)"),
|
|
Url: latestChapter.Url,
|
|
ThumbnailUrl: thumbnail,
|
|
}
|
|
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"]
|
|
if !ok || order.Value == "title" {
|
|
slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int {
|
|
return cmp.Compare(a.Title, b.Title)
|
|
})
|
|
} else if order.Value == "chapter" {
|
|
slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int {
|
|
return cmp.Compare(b.Number, a.Number)
|
|
})
|
|
} else if order.Value == "last" {
|
|
slices.SortStableFunc(mangaViewModels, func(a, b view.MangaViewModel) int {
|
|
aT, err := time.Parse("15:04 (02-01-06)", a.LastTime)
|
|
if err != nil {
|
|
return cmp.Compare(a.Title, b.Title)
|
|
}
|
|
bT, err := time.Parse("15:04 (02-01-06)", b.LastTime)
|
|
if err != nil {
|
|
return cmp.Compare(a.Title, b.Title)
|
|
}
|
|
return bT.Compare(aT)
|
|
})
|
|
}
|
|
|
|
nex = time.Now().UnixNano()
|
|
fmt.Printf("Sorting took %d ms\n", (nex-n)/1000000)
|
|
|
|
menuViewModel := view.MenuViewModel{
|
|
Settings: settings,
|
|
Mangas: mangaViewModels,
|
|
}
|
|
|
|
err = tmpl.Execute(w, menuViewModel)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
func (s *Server) HandleDelete(w http.ResponseWriter, r *http.Request) {
|
|
mangaStr := r.PostFormValue("mangaId")
|
|
|
|
if mangaStr == "" {
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
return
|
|
}
|
|
|
|
mangaId, err := strconv.Atoi(mangaStr)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
return
|
|
}
|
|
|
|
s.DbMgr.Delete(mangaId)
|
|
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
}
|
|
|
|
func (s *Server) HandleExit(w http.ResponseWriter, r *http.Request) {
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
|
|
// session, err := s.getSessionFromCookie(w, r)
|
|
// if err != nil {
|
|
// return
|
|
// }
|
|
|
|
go func() {
|
|
// session.Mutex.Lock()
|
|
// if session.PrevViewModel != nil {
|
|
// for _, img := range session.PrevViewModel.Images {
|
|
// delete(s.ImageBuffers, img.Path)
|
|
// }
|
|
// }
|
|
// if session.CurrViewModel != nil {
|
|
//
|
|
// for _, img := range session.CurrViewModel.Images {
|
|
// delete(s.ImageBuffers, img.Path)
|
|
// }
|
|
// }
|
|
// if session.NextViewModel != nil {
|
|
//
|
|
// for _, img := range session.NextViewModel.Images {
|
|
// delete(s.ImageBuffers, img.Path)
|
|
// }
|
|
// }
|
|
// session.Mutex.Unlock()
|
|
fmt.Println("Cleaned last Manga")
|
|
}()
|
|
}
|
|
|
|
func (s *Server) HandleCurrent(w http.ResponseWriter, r *http.Request) {
|
|
tmpl := template.Must(view.GetViewTemplate(view.Viewer))
|
|
session, err := s.getSessionFromCookie(w, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
mangaId, chapterId, err := s.Provider.GetTitleIdAndChapterId(session.CurrSubUrl)
|
|
if err != nil {
|
|
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
|
|
result = s.DbMgr.Db.Where("user_id = ?", session.User.Id).First(&manga, "manga_definition_id = ?", mangaId)
|
|
if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
manga = database.NewManga(mangaDef, session.User, time.Now().Unix())
|
|
}
|
|
|
|
var chapter database.Chapter
|
|
result = s.DbMgr.Db.Where("user_id = ?", session.User.Id).First(&chapter, "chapter_id = ?", chapterId)
|
|
if result.Error != nil && errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
chapterNumberStr := strings.Replace(chapterName, "ch_", "", 1)
|
|
chapter = database.NewChapter(chapterId, mangaId, session.User.Id, session.CurrSubUrl, chapterName, chapterNumberStr, time.Now().Unix())
|
|
} else {
|
|
chapter.TimeStampUnix = time.Now().Unix()
|
|
}
|
|
|
|
s.DbMgr.Db.Save(&mangaDef)
|
|
s.DbMgr.Db.Save(&manga)
|
|
s.DbMgr.Db.Save(&chapter)
|
|
|
|
err = tmpl.Execute(w, session.CurrViewModel)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
func (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) {
|
|
u := r.PathValue("url")
|
|
s.Mutex.RLock()
|
|
defer s.Mutex.RUnlock()
|
|
buf := s.ImageBuffers[u]
|
|
if buf == nil {
|
|
fmt.Printf("url: %s is nil\n", u)
|
|
w.WriteHeader(400)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "image/webp")
|
|
_, err := w.Write(buf)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
//go:embed favicon.ico
|
|
var ico []byte
|
|
|
|
func (s *Server) HandleFavicon(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "image/webp")
|
|
_, err := w.Write(ico)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
func (s *Server) HandleNext(w http.ResponseWriter, r *http.Request) {
|
|
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) {
|
|
s.Mutex.Lock()
|
|
for _, img := range viewModel.Images {
|
|
delete(s.ImageBuffers, img.Path)
|
|
}
|
|
s.Mutex.Unlock()
|
|
fmt.Println("Cleaned out of scope Last")
|
|
}(*session.PrevViewModel, s)
|
|
}
|
|
|
|
if session.NextViewModel == nil || session.NextSubUrl == "" {
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
return
|
|
}
|
|
|
|
session.PrevViewModel = session.CurrViewModel
|
|
session.CurrViewModel = session.NextViewModel
|
|
session.PrevSubUrl = session.CurrSubUrl
|
|
session.CurrSubUrl = session.NextSubUrl
|
|
|
|
go s.LoadNext(session)
|
|
|
|
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
|
}
|
|
func (s *Server) HandlePrev(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Println("Received Prev")
|
|
|
|
session, err := s.getSessionFromCookie(w, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if session.NextViewModel != nil {
|
|
go func(viewModel view.ImageViewModel, s *Server) {
|
|
s.Mutex.Lock()
|
|
for _, img := range viewModel.Images {
|
|
delete(s.ImageBuffers, img.Path)
|
|
}
|
|
s.Mutex.Unlock()
|
|
fmt.Println("Cleaned out of scope Last")
|
|
}(*session.NextViewModel, s)
|
|
}
|
|
|
|
if session.PrevViewModel == nil || session.PrevSubUrl == "" {
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
return
|
|
}
|
|
|
|
session.NextViewModel = session.CurrViewModel
|
|
session.CurrViewModel = session.PrevViewModel
|
|
session.NextSubUrl = session.CurrSubUrl
|
|
session.CurrSubUrl = session.PrevSubUrl
|
|
|
|
go s.LoadPrev(session)
|
|
|
|
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
|
}
|
|
|
|
func (s *Server) HandleSettingSet(w http.ResponseWriter, r *http.Request) {
|
|
settingName := r.PathValue("setting")
|
|
settingValue := r.PathValue("value")
|
|
|
|
var setting database.Setting
|
|
res := s.DbMgr.Db.First(&setting, "name = ?", settingName)
|
|
|
|
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
|
set := database.NewSetting(settingName, settingValue)
|
|
s.DbMgr.Db.Save(&set)
|
|
} else {
|
|
s.DbMgr.Db.Model(&setting).Update("value", settingValue)
|
|
}
|
|
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
}
|
|
|
|
func (s *Server) HandleSetting(w http.ResponseWriter, r *http.Request) {
|
|
settingName := r.PostFormValue("setting")
|
|
settingValue := r.PostFormValue(settingName)
|
|
|
|
var setting database.Setting
|
|
res := s.DbMgr.Db.First(&setting, "name = ?", settingName)
|
|
|
|
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
|
set := database.NewSetting(settingName, settingValue)
|
|
s.DbMgr.Db.Save(&set)
|
|
} else {
|
|
s.DbMgr.Db.Model(&setting).Update("value", settingValue)
|
|
}
|
|
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
}
|
|
|
|
func (s *Server) HandleNewQuery(w http.ResponseWriter, r *http.Request) {
|
|
sub := r.PostFormValue("subUrl")
|
|
|
|
url := fmt.Sprintf("/title/%s", sub)
|
|
|
|
session, err := s.getSessionFromCookie(w, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
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)
|
|
}
|