From a4880d18231db4f6fdbd4269f76f1b7a8ba7463f Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 31 May 2023 17:44:03 +0200 Subject: [PATCH 001/144] Initial api attempt (non-functional) --- internal/api/routes_heresphere.go | 533 ++++++++++++++++++++++++++++++ internal/api/server.go | 4 + 2 files changed, 537 insertions(+) create mode 100644 internal/api/routes_heresphere.go diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go new file mode 100644 index 00000000000..f1d7dc8a87c --- /dev/null +++ b/internal/api/routes_heresphere.go @@ -0,0 +1,533 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/go-chi/chi" + "github.com/stashapp/stash/internal/manager" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/txn" + "github.com/stashapp/stash/pkg/utils" +) + +// Based on HereSphere_JSON_API_Version_1.txt + +const heresphereJsonVersion = 1 + +const ( + heresphereGuest = 0 + heresphereMember = 1 + heresphereBadLogin = -1 +) + +type heresphereProjection string + +const ( + heresphereProjectionEquirectangular heresphereProjection = "equirectangular" + heresphereProjectionPerspective heresphereProjection = "perspective" + heresphereProjectionEquirectangular360 heresphereProjection = "equirectangular360" + heresphereProjectionFisheye heresphereProjection = "fisheye" + heresphereProjectionCubemap heresphereProjection = "cubemap" + heresphereProjectionEquirectangularCubemap heresphereProjection = "equiangularCubemap" +) + +type heresphereStereo string + +const ( + heresphereStereoMono heresphereStereo = "mono" + heresphereStereoSbs heresphereStereo = "sbs" + heresphereStereoTB heresphereStereo = "tb" +) + +type heresphereLens string + +const ( + heresphereLensLinear heresphereLens = "Linear" + heresphereLensMKX220 heresphereLens = "MKX220" + heresphereLensMKX200 heresphereLens = "MKX200" + heresphereLensVRCA220 heresphereLens = "VRCA220" +) + +type heresphereAuthReq struct { + username string `json:"username"` + password string `json:"password"` +} +type heresphereAuthResp struct { + auth_token string `json:"auth-token"` + access int `json:"access"` +} + +type heresphereBanner struct { + image string `json:"image"` + link string `json:"link"` +} +type heresphereIndexEntry struct { + name string `json:"name"` + list []string `json:"list"` +} +type heresphereIndex struct { + access int `json:"access"` + banner heresphereBanner `json:"banner"` + library []heresphereIndexEntry `json:"library"` +} +type heresphereVideoReq struct { + username string `json:"username"` + password string `json:"password"` + needsMediaSource bool `json:"needsMediaSource,omitempty"` +} +type heresphereVideoScript struct { + name string `json:"name"` + url string `json:"url"` + rating float32 `json:"rating"` +} +type heresphereVideoSubtitle struct { + name string `json:"name"` + language string `json:"language"` + url string `json:"url"` +} +type heresphereVideoTag struct { + // Should start with any of the following: "Scene:", "Category:", "Talent:", "Studio:", "Position:" + name string `json:"name"` + start float64 `json:"start"` + end float64 `json:"end"` + track int `json:"track"` + rating float32 `json:"rating"` +} +type heresphereVideoMediaSource struct { + resolution int `json:"resolution"` + height int `json:"height"` + width int `json:"width"` + // In bytes + size int64 `json:"size"` + url string `json:"url"` +} +type heresphereVideoMedia struct { + // Media type (h265 etc.) + name string `json:"name"` + sources []heresphereVideoMediaSource `json:"sources"` +} +type heresphereVideoEntry struct { + access int `json:"access"` + title string `json:"title"` + description string `json:"description"` + thumbnailImage string `json:"thumbnailImage"` + thumbnailVideo string `json:"thumbnailVideo"` + dateReleased string `json:"dateReleased"` + dateAdded string `json:"dateAdded"` + duration float64 `json:"duration"` + rating float32 `json:"rating"` + favorites float32 `json:"favorites"` + comments int `json:"comments"` + isFavorite bool `json:"isFavorite"` + projection heresphereProjection `json:"projection"` + stereo heresphereStereo `json:"stereo"` + isEyeSwapped bool `json:"isEyeSwapped"` + fov float32 `json:"fov"` + lens heresphereLens `json:"lens"` + cameraIPD float32 `json:"cameraIPD"` + hsp string `json:"hsp,omitempty"` + eventServer string `json:"eventServer,omitempty"` + scripts []heresphereVideoScript `json:"scripts"` + subtitles []heresphereVideoSubtitle `json:"subtitles"` + tags []heresphereVideoTag `json:"tags"` + media []heresphereVideoMedia `json:"media"` + writeFavorite bool `json:"writeFavorite"` + writeRating bool `json:"writeRating"` + writeTags bool `json:"writeTags"` + writeHSP bool `json:"writeHSP"` +} +type heresphereVideoEntryShort struct { + link string `json:"link"` + title string `json:"title"` + dateReleased string `json:"dateReleased"` + dateAdded string `json:"dateAdded"` + duration float64 `json:"duration"` + rating float32 `json:"rating"` + favorites int `json:"favorites"` + comments int `json:"comments"` + isFavorite bool `json:"isFavorite"` + tags []heresphereVideoTag `json:"tags"` +} +type heresphereVideoEntryUpdate struct { + username string `json:"username"` + password string `json:"password"` + isFavorite bool `json:"isFavorite"` + rating float32 `json:"rating"` + tags []heresphereVideoTag `json:"tags"` + //In base64 + hsp string `json:"hsp"` + deleteFile bool `json:"deleteFile"` +} +type heresphereVideoEvent struct { + username string `json:"username"` + id string `json:"id"` + title string `json:"title"` + event int `json:"event"` + time float64 `json:"time"` + speed float32 `json:"speed"` + utc float64 `json:"utc"` + connectionKey string `json:"connectionKey"` +} + +type heresphereRoutes struct { + txnManager txn.Manager + repository manager.Repository +} + +func (rs heresphereRoutes) Routes() chi.Router { + r := chi.NewRouter() + + r.Route("/", func(r chi.Router) { + r.Use(rs.HeresphereCtx) + r.Post("/", rs.heresphereLogin) + + r.Post("/auth", rs.heresphereLoginToken) + r.Post("/scan", rs.heresphereScan) + r.Route("/{sceneId}", func(r chi.Router) { + r.Post("/", rs.heresphereVideoData) + //Ours + r.Get("/hsp", rs.heresphereVideoHsp) + r.Post("/event", rs.heresphereVideoEvent) + }) + }) + + return r +} + +func (rs heresphereRoutes) heresphereVideoEvent(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +func getVideoTags(scene *models.Scene) []heresphereVideoTag { + var processedTags []heresphereVideoTag + + for _, sceneTags := range scene.GalleryIDs.List() { + genTag := heresphereVideoTag{ + name: fmt.Sprintf("Gallery:%s", sceneTags), + start: 0.0, + end: 1.0, + track: 0, + rating: 0, + } + processedTags = append(processedTags, genTag) + } + + for _, sceneTags := range scene.TagIDs.List() { + genTag := heresphereVideoTag{ + name: fmt.Sprintf("Tag:%s", sceneTags), + start: 0.0, + end: 1.0, + track: 0, + rating: 0, + } + processedTags = append(processedTags, genTag) + } + + for _, sceneTags := range scene.PerformerIDs.List() { + genTag := heresphereVideoTag{ + name: fmt.Sprintf("Talent:%s", sceneTags), + start: 0.0, + end: 1.0, + track: 0, + rating: 0, + } + processedTags = append(processedTags, genTag) + } + + for _, sceneTags := range scene.Movies.List() { + genTag := heresphereVideoTag{ + name: fmt.Sprintf("Movie:%s", sceneTags), + start: 0.0, + end: 1.0, + track: 0, + rating: 0, + } + processedTags = append(processedTags, genTag) + } + + for _, sceneTags := range scene.StashIDs.List() { + genTag := heresphereVideoTag{ + name: fmt.Sprintf("Scene:%s", sceneTags), + start: 0.0, + end: 1.0, + track: 0, + rating: 0, + } + processedTags = append(processedTags, genTag) + } + + genTag := heresphereVideoTag{ + name: fmt.Sprintf("Studio:%s", scene.StudioID), + start: 0.0, + end: 1.0, + track: 0, + rating: 0, + } + processedTags = append(processedTags, genTag) + + return processedTags +} +func getVideoScripts(scene *models.Scene) []heresphereVideoScript { + processedScript := heresphereVideoScript{ + name: "Default script", + url: fmt.Sprintf("/%s/funscript", scene.ID), + rating: 2.5, + } + processedScripts := []heresphereVideoScript{processedScript} + return processedScripts +} +func getVideoSubtitles(scene *models.Scene) []heresphereVideoSubtitle { + var processedSubtitles []heresphereVideoSubtitle + + /*var captions []*models.VideoCaption + readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + var err error + primaryFile := s.Files.Primary() + if primaryFile == nil { + return nil + } + + captions, err = rs.captionFinder.GetCaptions(ctx, primaryFile.Base().ID) + + return err + }) + if errors.Is(readTxnErr, context.Canceled) { + return + } + if readTxnErr != nil { + logger.Warnf("read transaction error on fetch scene captions: %v", readTxnErr) + http.Error(w, readTxnErr.Error(), http.StatusInternalServerError) + return + } + + for _, caption := range captions { + if lang != caption.LanguageCode || ext != caption.CaptionType { + continue + } + + sub, err := video.ReadSubs(caption.Path(s.Path)) + if err != nil { + logger.Warnf("error while reading subs: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var buf bytes.Buffer + + err = sub.WriteToWebVTT(&buf) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/vtt") + utils.ServeStaticContent(w, r, buf.Bytes()) + return + }*/ + + return processedSubtitles +} +func getVideoMedia(scene *models.Scene) []heresphereVideoMedia { + var processedMedia []heresphereVideoMedia + + //TODO: Gather all files by Format, use a map/dict + mediaFile := scene.Files.Primary() + processedEntry := heresphereVideoMediaSource{ + resolution: mediaFile.Height, + height: mediaFile.Height, + width: mediaFile.Width, + size: mediaFile.Size, + url: fmt.Sprintf("/%s/stream"), + } + processedSources := []heresphereVideoMediaSource{processedEntry} + processedMedia = append(processedMedia, heresphereVideoMedia{ + name: mediaFile.Format, + sources: processedSources, + }) + + return processedMedia +} + +func (rs heresphereRoutes) heresphereScan(w http.ResponseWriter, r *http.Request) { + var scenes []*models.Scene + _ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + var err error + scenes, err = rs.repository.Scene.All(ctx) + return err + }) + if scenes == nil { + http.Error(w, http.StatusText(500), 500) + return + } + + // Processing each scene and creating a new list + var processedScenes []heresphereVideoEntryShort + for _, scene := range scenes { + isFavorite := *scene.Rating > 85 + + // Perform the necessary processing on each scene + processedScene := heresphereVideoEntryShort{ + link: fmt.Sprintf("/heresphere/%s", scene.ID), + title: scene.Title, + dateReleased: scene.Date.Format("2006-02-01"), + duration: scene.PlayDuration, + rating: float32(*scene.Rating) * 0.05, // 0-5 + favorites: 0, + comments: scene.OCounter, + isFavorite: isFavorite, + tags: getVideoTags(scene), + } + processedScenes = append(processedScenes, processedScene) + } + + // Create a JSON encoder for the response writer + buf := &bytes.Buffer{} + encoder := json.NewEncoder(buf) + + // Write the JSON response + err := encoder.Encode(processedScenes) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + utils.ServeStaticContent(w, r, buf.Bytes()) +} + +func (rs heresphereRoutes) heresphereVideoHsp(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +func (rs heresphereRoutes) heresphereVideoData(w http.ResponseWriter, r *http.Request) { + sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) + if err != nil { + http.Error(w, "Missing sceneId", http.StatusBadRequest) + return + } + + var user heresphereVideoReq + err = json.NewDecoder(r.Body).Decode(&user) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // TODO: This endpoint can receive 2 types of requests + // One is a video request (heresphereVideoReq) + // Other is an update (heresphereVideoEntryUpdate) + + var scene *models.Scene + _ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + var err error + scene, err = rs.repository.Scene.Find(ctx, sceneID) + return err + }) + if scene == nil { + http.Error(w, http.StatusText(404), 404) + return + } + + isFavorite := *scene.Rating > 85 + processedScene := heresphereVideoEntry{ + access: 1, + title: scene.Title, + description: scene.Details, + thumbnailImage: "", + thumbnailVideo: "", + dateReleased: scene.Date.Format("2006-02-01"), + duration: scene.PlayDuration, + rating: float32(*scene.Rating) * 0.05, // 0-5 + favorites: 0, + comments: scene.OCounter, + isFavorite: isFavorite, + projection: heresphereProjectionEquirectangular, + stereo: heresphereStereoMono, + isEyeSwapped: false, + fov: 180, + lens: heresphereLensLinear, + cameraIPD: 6.5, + hsp: fmt.Sprintf("/heresphere/%s/hsp", scene.ID), + eventServer: fmt.Sprintf("/heresphere/%s/event", scene.ID), + scripts: getVideoScripts(scene), + subtitles: getVideoSubtitles(scene), + tags: getVideoTags(scene), + media: getVideoMedia(scene), + writeFavorite: false, + writeRating: false, + writeTags: false, + writeHSP: false, + } + + // Create a JSON encoder for the response writer + buf := &bytes.Buffer{} + encoder := json.NewEncoder(buf) + + // Write the JSON response + err = encoder.Encode(processedScene) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + utils.ServeStaticContent(w, r, buf.Bytes()) +} + +func (rs heresphereRoutes) heresphereLogin(w http.ResponseWriter, r *http.Request) { + if r.Body == nil { + http.Error(w, "Missing body", http.StatusBadRequest) + return + } + + var user heresphereAuthReq + err := json.NewDecoder(r.Body).Decode(&user) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // TODO: Auth + w.WriteHeader(http.StatusOK) +} + +func (rs heresphereRoutes) heresphereLoginToken(w http.ResponseWriter, r *http.Request) { + if r.Body == nil { + http.Error(w, "Missing body", http.StatusBadRequest) + return + } + + var user heresphereAuthReq + err := json.NewDecoder(r.Body).Decode(&user) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + //TODO: Auth + + auth := &heresphereAuthResp{ + auth_token: "yes", + access: heresphereMember, + } + + // Create a JSON encoder for the response writer + buf, err := json.Marshal(auth) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + utils.ServeStaticContent(w, r, buf) +} + +func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("HereSphere-JSON-Version", strconv.Itoa(heresphereJsonVersion)) + next.ServeHTTP(w, r) + }) +} diff --git a/internal/api/server.go b/internal/api/server.go index cfc57b3dd62..e1327c53617 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -178,6 +178,10 @@ func Start() error { tagFinder: txnManager.Tag, }.Routes()) r.Mount("/downloads", downloadsRoutes{}.Routes()) + r.Mount("/heresphere", heresphereRoutes{ + txnManager: txnManager, + repository: txnManager, + }.Routes()) r.HandleFunc("/css", cssHandler(c, pluginCache)) r.HandleFunc("/javascript", javascriptHandler(c, pluginCache)) From 8e2250cbb4bfea5ffbd9248e5101295f5f2c9b76 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 1 Jun 2023 17:02:49 +0200 Subject: [PATCH 002/144] HereSphere API now works --- internal/api/context_keys.go | 1 + internal/api/routes_heresphere.go | 754 ++++++++++++++++++------------ internal/api/server.go | 6 +- 3 files changed, 457 insertions(+), 304 deletions(-) diff --git a/internal/api/context_keys.go b/internal/api/context_keys.go index 8731f75c301..191370a5d49 100644 --- a/internal/api/context_keys.go +++ b/internal/api/context_keys.go @@ -13,4 +13,5 @@ const ( tagKey downloadKey imageKey + heresphereKey ) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index f1d7dc8a87c..54aec07ad8a 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -1,182 +1,183 @@ package api import ( - "bytes" "context" "encoding/json" + "errors" "fmt" "net/http" "strconv" "github.com/go-chi/chi" "github.com/stashapp/stash/internal/manager" + "github.com/stashapp/stash/pkg/file" + "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/txn" - "github.com/stashapp/stash/pkg/utils" ) // Based on HereSphere_JSON_API_Version_1.txt -const heresphereJsonVersion = 1 +const HeresphereJsonVersion = 1 const ( - heresphereGuest = 0 - heresphereMember = 1 - heresphereBadLogin = -1 + HeresphereGuest = 0 + HeresphereMember = 1 + HeresphereBadLogin = -1 ) -type heresphereProjection string +type HeresphereProjection string const ( - heresphereProjectionEquirectangular heresphereProjection = "equirectangular" - heresphereProjectionPerspective heresphereProjection = "perspective" - heresphereProjectionEquirectangular360 heresphereProjection = "equirectangular360" - heresphereProjectionFisheye heresphereProjection = "fisheye" - heresphereProjectionCubemap heresphereProjection = "cubemap" - heresphereProjectionEquirectangularCubemap heresphereProjection = "equiangularCubemap" + HeresphereProjectionEquirectangular HeresphereProjection = "equirectangular" + HeresphereProjectionPerspective HeresphereProjection = "perspective" + HeresphereProjectionEquirectangular360 HeresphereProjection = "equirectangular360" + HeresphereProjectionFisheye HeresphereProjection = "fisheye" + HeresphereProjectionCubemap HeresphereProjection = "cubemap" + HeresphereProjectionEquirectangularCubemap HeresphereProjection = "equiangularCubemap" ) -type heresphereStereo string +type HeresphereStereo string const ( - heresphereStereoMono heresphereStereo = "mono" - heresphereStereoSbs heresphereStereo = "sbs" - heresphereStereoTB heresphereStereo = "tb" + HeresphereStereoMono HeresphereStereo = "mono" + HeresphereStereoSbs HeresphereStereo = "sbs" + HeresphereStereoTB HeresphereStereo = "tb" ) -type heresphereLens string +type HeresphereLens string const ( - heresphereLensLinear heresphereLens = "Linear" - heresphereLensMKX220 heresphereLens = "MKX220" - heresphereLensMKX200 heresphereLens = "MKX200" - heresphereLensVRCA220 heresphereLens = "VRCA220" + HeresphereLensLinear HeresphereLens = "Linear" + HeresphereLensMKX220 HeresphereLens = "MKX220" + HeresphereLensMKX200 HeresphereLens = "MKX200" + HeresphereLensVRCA220 HeresphereLens = "VRCA220" ) -type heresphereAuthReq struct { - username string `json:"username"` - password string `json:"password"` +type HeresphereAuthReq struct { + Username string `json:"username"` + Password string `json:"password"` + NeedsMediaSource bool `json:"needsMediaSource,omitempty"` } -type heresphereAuthResp struct { - auth_token string `json:"auth-token"` - access int `json:"access"` +type HeresphereAuthResp struct { + AuthToken string `json:"auth-token"` + Access int `json:"access"` } -type heresphereBanner struct { - image string `json:"image"` - link string `json:"link"` +type HeresphereBanner struct { + Image string `json:"image"` + Link string `json:"link"` } -type heresphereIndexEntry struct { - name string `json:"name"` - list []string `json:"list"` +type HeresphereIndexEntry struct { + Name string `json:"name"` + List []string `json:"list"` } -type heresphereIndex struct { - access int `json:"access"` - banner heresphereBanner `json:"banner"` - library []heresphereIndexEntry `json:"library"` +type HeresphereIndex struct { + Access int `json:"access"` + //Banner HeresphereBanner `json:"banner"` + Library []HeresphereIndexEntry `json:"library"` } -type heresphereVideoReq struct { - username string `json:"username"` - password string `json:"password"` - needsMediaSource bool `json:"needsMediaSource,omitempty"` +type HeresphereVideoScript struct { + Name string `json:"name"` + Url string `json:"url"` + //Rating float32 `json:"rating,omitempty"` + // TODO: Floats become null, same with lists, should have default value instead + // This is technically an api violation } -type heresphereVideoScript struct { - name string `json:"name"` - url string `json:"url"` - rating float32 `json:"rating"` +type HeresphereVideoSubtitle struct { + Name string `json:"name"` + Language string `json:"language"` + Url string `json:"url"` } -type heresphereVideoSubtitle struct { - name string `json:"name"` - language string `json:"language"` - url string `json:"url"` -} -type heresphereVideoTag struct { +type HeresphereVideoTag struct { // Should start with any of the following: "Scene:", "Category:", "Talent:", "Studio:", "Position:" - name string `json:"name"` - start float64 `json:"start"` - end float64 `json:"end"` - track int `json:"track"` - rating float32 `json:"rating"` + Name string `json:"name"` + /*Start float64 `json:"start,omitempty"` + End float64 `json:"end,omitempty"` + Track int `json:"track,omitempty"` + Rating float32 `json:"rating,omitempty"`*/ } -type heresphereVideoMediaSource struct { - resolution int `json:"resolution"` - height int `json:"height"` - width int `json:"width"` +type HeresphereVideoMediaSource struct { + Resolution int `json:"resolution"` + Height int `json:"height"` + Width int `json:"width"` // In bytes - size int64 `json:"size"` - url string `json:"url"` + Size int64 `json:"size"` + Url string `json:"url"` } -type heresphereVideoMedia struct { +type HeresphereVideoMedia struct { // Media type (h265 etc.) - name string `json:"name"` - sources []heresphereVideoMediaSource `json:"sources"` + Name string `json:"name"` + Sources []HeresphereVideoMediaSource `json:"sources"` } -type heresphereVideoEntry struct { - access int `json:"access"` - title string `json:"title"` - description string `json:"description"` - thumbnailImage string `json:"thumbnailImage"` - thumbnailVideo string `json:"thumbnailVideo"` - dateReleased string `json:"dateReleased"` - dateAdded string `json:"dateAdded"` - duration float64 `json:"duration"` - rating float32 `json:"rating"` - favorites float32 `json:"favorites"` - comments int `json:"comments"` - isFavorite bool `json:"isFavorite"` - projection heresphereProjection `json:"projection"` - stereo heresphereStereo `json:"stereo"` - isEyeSwapped bool `json:"isEyeSwapped"` - fov float32 `json:"fov"` - lens heresphereLens `json:"lens"` - cameraIPD float32 `json:"cameraIPD"` - hsp string `json:"hsp,omitempty"` - eventServer string `json:"eventServer,omitempty"` - scripts []heresphereVideoScript `json:"scripts"` - subtitles []heresphereVideoSubtitle `json:"subtitles"` - tags []heresphereVideoTag `json:"tags"` - media []heresphereVideoMedia `json:"media"` - writeFavorite bool `json:"writeFavorite"` - writeRating bool `json:"writeRating"` - writeTags bool `json:"writeTags"` - writeHSP bool `json:"writeHSP"` +type HeresphereVideoEntry struct { + Access int `json:"access"` + Title string `json:"title"` + Description string `json:"description"` + ThumbnailImage string `json:"thumbnailImage"` + ThumbnailVideo string `json:"thumbnailVideo,omitempty"` + DateReleased string `json:"dateReleased"` + DateAdded string `json:"dateAdded"` + Duration uint `json:"duration,omitempty"` + /*Rating float32 `json:"rating,omitempty"` + Favorites float32 `json:"favorites,omitempty"` + Comments int `json:"comments"`*/ + IsFavorite bool `json:"isFavorite"` + Projection HeresphereProjection `json:"projection"` + Stereo HeresphereStereo `json:"stereo"` + //IsEyeSwapped bool `json:"isEyeSwapped"` + Fov float32 `json:"fov,omitempty"` + Lens HeresphereLens `json:"lens"` + //CameraIPD float32 `json:"cameraIPD,omitempty"` + /*Hsp string `json:"hsp,omitempty"` + EventServer string `json:"eventServer,omitempty"`*/ + Scripts []HeresphereVideoScript `json:"scripts,omitempty"` + Subtitles []HeresphereVideoSubtitle `json:"subtitles,omitempty"` + Tags []HeresphereVideoTag `json:"tags,omitempty"` + Media []HeresphereVideoMedia `json:"media,omitempty"` + WriteFavorite bool `json:"writeFavorite"` + WriteRating bool `json:"writeRating"` + WriteTags bool `json:"writeTags"` + WriteHSP bool `json:"writeHSP"` } -type heresphereVideoEntryShort struct { - link string `json:"link"` - title string `json:"title"` - dateReleased string `json:"dateReleased"` - dateAdded string `json:"dateAdded"` - duration float64 `json:"duration"` - rating float32 `json:"rating"` - favorites int `json:"favorites"` - comments int `json:"comments"` - isFavorite bool `json:"isFavorite"` - tags []heresphereVideoTag `json:"tags"` +type HeresphereVideoEntryShort struct { + Link string `json:"link"` + Title string `json:"title"` + DateReleased string `json:"dateReleased"` + DateAdded string `json:"dateAdded"` + Duration uint `json:"duration,omitempty"` + /*Rating float32 `json:"rating,omitempty"` + Favorites int `json:"favorites"` + Comments int `json:"comments"`*/ + IsFavorite bool `json:"isFavorite"` + Tags []HeresphereVideoTag `json:"tags"` } -type heresphereVideoEntryUpdate struct { - username string `json:"username"` - password string `json:"password"` - isFavorite bool `json:"isFavorite"` - rating float32 `json:"rating"` - tags []heresphereVideoTag `json:"tags"` +type HeresphereVideoEntryUpdate struct { + Username string `json:"username"` + Password string `json:"password"` + IsFavorite bool `json:"isFavorite"` + Rating float32 `json:"rating,omitempty"` + Tags []HeresphereVideoTag `json:"tags"` //In base64 - hsp string `json:"hsp"` - deleteFile bool `json:"deleteFile"` + Hsp string `json:"hsp"` + DeleteFile bool `json:"deleteFile"` } -type heresphereVideoEvent struct { - username string `json:"username"` - id string `json:"id"` - title string `json:"title"` - event int `json:"event"` - time float64 `json:"time"` - speed float32 `json:"speed"` - utc float64 `json:"utc"` - connectionKey string `json:"connectionKey"` +type HeresphereVideoEvent struct { + Username string `json:"username"` + Id string `json:"id"` + Title string `json:"title"` + Event int `json:"event"` + Time float64 `json:"time,omitempty"` + Speed float32 `json:"speed,omitempty"` + Utc float64 `json:"utc,omitempty"` + ConnectionKey string `json:"connectionKey"` } type heresphereRoutes struct { - txnManager txn.Manager - repository manager.Repository + txnManager txn.Manager + sceneFinder SceneFinder + fileFinder file.Finder + repository manager.Repository } func (rs heresphereRoutes) Routes() chi.Router { @@ -184,105 +185,153 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Route("/", func(r chi.Router) { r.Use(rs.HeresphereCtx) - r.Post("/", rs.heresphereLogin) + r.Post("/", rs.HeresphereLogin) + r.Get("/", rs.HeresphereIndex) + r.Head("/", rs.HeresphereIndex) - r.Post("/auth", rs.heresphereLoginToken) - r.Post("/scan", rs.heresphereScan) + r.Post("/auth", rs.HeresphereLoginToken) + r.Post("/scan", rs.HeresphereScan) r.Route("/{sceneId}", func(r chi.Router) { - r.Post("/", rs.heresphereVideoData) + r.Use(rs.HeresphereSceneCtx) + + r.Post("/", rs.HeresphereVideoData) + r.Get("/", rs.HeresphereVideoData) //Ours - r.Get("/hsp", rs.heresphereVideoHsp) - r.Post("/event", rs.heresphereVideoEvent) + r.Get("/hsp", rs.HeresphereVideoHsp) + r.Post("/event", rs.HeresphereVideoEvent) }) }) return r } -func (rs heresphereRoutes) heresphereVideoEvent(w http.ResponseWriter, r *http.Request) { +func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) + // TODO: Auth } -func getVideoTags(scene *models.Scene) []heresphereVideoTag { - var processedTags []heresphereVideoTag +// TODO: This probably isnt necessary +func relUrlToAbs(r *http.Request, rel string) string { + // Get the scheme (http or https) from the request + scheme := "http" + if r.TLS != nil { + scheme = "https" + } - for _, sceneTags := range scene.GalleryIDs.List() { - genTag := heresphereVideoTag{ - name: fmt.Sprintf("Gallery:%s", sceneTags), - start: 0.0, - end: 1.0, - track: 0, - rating: 0, - } - processedTags = append(processedTags, genTag) + // Get the host from the request + host := r.Host + + // Combine the scheme, host, and relative path to form the absolute URL + return fmt.Sprintf("%s://%s%s", scheme, host, rel) +} + +func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoTag { + processedTags := []HeresphereVideoTag{} + + testTag := HeresphereVideoTag{ + Name: "Test:a", + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ } + processedTags = append(processedTags, testTag) + + if scene.LoadRelationships(ctx, rs.repository.Scene) == nil { + if scene.GalleryIDs.Loaded() { + for _, sceneTags := range scene.GalleryIDs.List() { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Gallery:%v", sceneTags), + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ + } + processedTags = append(processedTags, genTag) + } + } - for _, sceneTags := range scene.TagIDs.List() { - genTag := heresphereVideoTag{ - name: fmt.Sprintf("Tag:%s", sceneTags), - start: 0.0, - end: 1.0, - track: 0, - rating: 0, + if scene.TagIDs.Loaded() { + for _, sceneTags := range scene.TagIDs.List() { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Tag:%v", sceneTags), + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ + } + processedTags = append(processedTags, genTag) + } } - processedTags = append(processedTags, genTag) - } - for _, sceneTags := range scene.PerformerIDs.List() { - genTag := heresphereVideoTag{ - name: fmt.Sprintf("Talent:%s", sceneTags), - start: 0.0, - end: 1.0, - track: 0, - rating: 0, + if scene.PerformerIDs.Loaded() { + for _, sceneTags := range scene.PerformerIDs.List() { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Talent:%s", sceneTags), + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ + } + processedTags = append(processedTags, genTag) + } } - processedTags = append(processedTags, genTag) - } - for _, sceneTags := range scene.Movies.List() { - genTag := heresphereVideoTag{ - name: fmt.Sprintf("Movie:%s", sceneTags), - start: 0.0, - end: 1.0, - track: 0, - rating: 0, + if scene.Movies.Loaded() { + for _, sceneTags := range scene.Movies.List() { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Movie:%v", sceneTags), + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ + } + processedTags = append(processedTags, genTag) + } } - processedTags = append(processedTags, genTag) - } - for _, sceneTags := range scene.StashIDs.List() { - genTag := heresphereVideoTag{ - name: fmt.Sprintf("Scene:%s", sceneTags), - start: 0.0, - end: 1.0, - track: 0, - rating: 0, + if scene.StashIDs.Loaded() { + //TODO: Markers have timestamps? + for _, sceneTags := range scene.StashIDs.List() { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Scene:%v", sceneTags), + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ + } + processedTags = append(processedTags, genTag) + } } - processedTags = append(processedTags, genTag) } - genTag := heresphereVideoTag{ - name: fmt.Sprintf("Studio:%s", scene.StudioID), - start: 0.0, - end: 1.0, - track: 0, - rating: 0, + if scene.StudioID != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Studio:%v", scene.StudioID), + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ + } + processedTags = append(processedTags, genTag) } - processedTags = append(processedTags, genTag) return processedTags } -func getVideoScripts(scene *models.Scene) []heresphereVideoScript { - processedScript := heresphereVideoScript{ - name: "Default script", - url: fmt.Sprintf("/%s/funscript", scene.ID), - rating: 2.5, +func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoScript { + //TODO: Check if exists + processedScript := HeresphereVideoScript{ + Name: "Default script", + Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/funscript", scene.ID)), + //Rating: 2.5, } - processedScripts := []heresphereVideoScript{processedScript} + processedScripts := []HeresphereVideoScript{processedScript} return processedScripts } -func getVideoSubtitles(scene *models.Scene) []heresphereVideoSubtitle { - var processedSubtitles []heresphereVideoSubtitle +func (rs heresphereRoutes) getVideoSubtitles(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { + processedSubtitles := []HeresphereVideoSubtitle{} + + //TODO: /scene/123/caption?lang=00&type=srt /*var captions []*models.VideoCaption readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { @@ -332,158 +381,224 @@ func getVideoSubtitles(scene *models.Scene) []heresphereVideoSubtitle { return processedSubtitles } -func getVideoMedia(scene *models.Scene) []heresphereVideoMedia { - var processedMedia []heresphereVideoMedia +func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoMedia { + processedMedia := []HeresphereVideoMedia{} - //TODO: Gather all files by Format, use a map/dict + //TODO: Gather also secondary files, by Format, use a map/dict mediaFile := scene.Files.Primary() - processedEntry := heresphereVideoMediaSource{ - resolution: mediaFile.Height, - height: mediaFile.Height, - width: mediaFile.Width, - size: mediaFile.Size, - url: fmt.Sprintf("/%s/stream"), + if mediaFile != nil { + processedEntry := HeresphereVideoMediaSource{ + Resolution: mediaFile.Height, + Height: mediaFile.Height, + Width: mediaFile.Width, + Size: mediaFile.Size, + Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/stream", scene.ID)), + } + processedSources := []HeresphereVideoMediaSource{processedEntry} + processedMedia = append(processedMedia, HeresphereVideoMedia{ + Name: mediaFile.Format, + Sources: processedSources, + }) } - processedSources := []heresphereVideoMediaSource{processedEntry} - processedMedia = append(processedMedia, heresphereVideoMedia{ - name: mediaFile.Format, - sources: processedSources, - }) + //TODO: Transcode etc. /scene/%v/stream.mp4?resolution=ORIGINAL return processedMedia } -func (rs heresphereRoutes) heresphereScan(w http.ResponseWriter, r *http.Request) { +func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { + /*banner := HeresphereBanner{ + Image: relUrlToAbs(r, "/apple-touch-icon.png"), + Link: relUrlToAbs(r, "/"), + }*/ + var scenes []*models.Scene - _ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + if err := rs.repository.WithTxn(r.Context(), func(ctx context.Context) error { var err error scenes, err = rs.repository.Scene.All(ctx) return err - }) - if scenes == nil { - http.Error(w, http.StatusText(500), 500) + }); err != nil { + http.Error(w, "Failed to fetch scenes!", http.StatusInternalServerError) return } - // Processing each scene and creating a new list - var processedScenes []heresphereVideoEntryShort + var sceneUrls []string for _, scene := range scenes { - isFavorite := *scene.Rating > 85 + sceneUrls = append(sceneUrls, relUrlToAbs(r, fmt.Sprintf("/heresphere/%v", scene.ID))) + } + + library := HeresphereIndexEntry{ + Name: "All", + List: sceneUrls, + } + idx := HeresphereIndex{ + Access: HeresphereMember, + //Banner: banner, + Library: []HeresphereIndexEntry{library}, + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err := json.NewEncoder(w).Encode(idx) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} +func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { + //TODO: Auth + var scenes []*models.Scene + if err := rs.repository.WithTxn(r.Context(), func(ctx context.Context) error { + var err error + scenes, err = rs.repository.Scene.All(ctx) + return err + }); err != nil { + http.Error(w, "Failed to fetch scenes!", http.StatusInternalServerError) + return + } + + // Processing each scene and creating a new list + var processedScenes []HeresphereVideoEntryShort + for _, scene := range scenes { // Perform the necessary processing on each scene - processedScene := heresphereVideoEntryShort{ - link: fmt.Sprintf("/heresphere/%s", scene.ID), - title: scene.Title, - dateReleased: scene.Date.Format("2006-02-01"), - duration: scene.PlayDuration, - rating: float32(*scene.Rating) * 0.05, // 0-5 - favorites: 0, - comments: scene.OCounter, - isFavorite: isFavorite, - tags: getVideoTags(scene), + processedScene := HeresphereVideoEntryShort{ + Link: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v", scene.ID)), + Title: scene.GetTitle(), + DateReleased: scene.CreatedAt.Format("2006-01-02"), + DateAdded: scene.CreatedAt.Format("2006-01-02"), + Duration: 60, + /*Rating: 2.5, + Favorites: 0, + Comments: scene.OCounter,*/ + IsFavorite: false, + Tags: rs.getVideoTags(r.Context(), r, scene), + } + if scene.Date != nil { + processedScene.DateReleased = scene.Date.Format("2006-01-02") } + if scene.Rating != nil { + isFavorite := *scene.Rating > 85 + //processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 + processedScene.IsFavorite = isFavorite + } + //TODO: panic="relationship has not been loaded" + /*primaryFile := scene.Files.Primary() + if primaryFile != nil { + processedScene.Duration = handleFloat64Value(primaryFile.Duration) + }*/ processedScenes = append(processedScenes, processedScene) } // Create a JSON encoder for the response writer - buf := &bytes.Buffer{} - encoder := json.NewEncoder(buf) - - // Write the JSON response - err := encoder.Encode(processedScenes) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err := json.NewEncoder(w).Encode(processedScenes) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - - utils.ServeStaticContent(w, r, buf.Bytes()) } -func (rs heresphereRoutes) heresphereVideoHsp(w http.ResponseWriter, r *http.Request) { +func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Request) { + //TODO: Auth w.WriteHeader(http.StatusNotImplemented) } -func (rs heresphereRoutes) heresphereVideoData(w http.ResponseWriter, r *http.Request) { - sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) - if err != nil { - http.Error(w, "Missing sceneId", http.StatusBadRequest) - return +func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) { + //TODO: This + //TODO: Auth + +} + +func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { + scene := r.Context().Value(heresphereKey).(*models.Scene) + + // This endpoint can receive 2 types of requests + // One is a video request (HeresphereAuthReq) + // Other is an update (HeresphereVideoEntryUpdate) + + user := HeresphereAuthReq{ + NeedsMediaSource: true, } + userupd := HeresphereVideoEntryUpdate{} - var user heresphereVideoReq - err = json.NewDecoder(r.Body).Decode(&user) + err := json.NewDecoder(r.Body).Decode(&user) if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return + err = json.NewDecoder(r.Body).Decode(&userupd) + if err == nil { + rs.HeresphereVideoDataUpdate(w, r) + return + } + + /*http.Error(w, err.Error(), http.StatusBadRequest) + return*/ } - // TODO: This endpoint can receive 2 types of requests - // One is a video request (heresphereVideoReq) - // Other is an update (heresphereVideoEntryUpdate) + //TODO: Auth - var scene *models.Scene - _ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - var err error - scene, err = rs.repository.Scene.Find(ctx, sceneID) - return err - }) - if scene == nil { - http.Error(w, http.StatusText(404), 404) - return + processedScene := HeresphereVideoEntry{ + Access: HeresphereMember, + Title: scene.GetTitle(), + Description: scene.Details, + ThumbnailImage: relUrlToAbs(r, fmt.Sprintf("/scene/%v/screenshot", scene.ID)), + ThumbnailVideo: relUrlToAbs(r, fmt.Sprintf("/scene/%v/preview", scene.ID)), + DateReleased: scene.CreatedAt.Format("2006-01-02"), + DateAdded: scene.CreatedAt.Format("2006-01-02"), + Duration: 60.0, + /*Rating: 2.5, + Favorites: 0, + Comments: scene.OCounter,*/ + IsFavorite: false, + Projection: HeresphereProjectionPerspective, // Default to flat cause i have no idea + Stereo: HeresphereStereoMono, // Default to flat cause i have no idea + //IsEyeSwapped: false, + Fov: 180, + Lens: HeresphereLensLinear, + /*CameraIPD: 6.5, + Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp", scene.ID)), + EventServer: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/event", scene.ID)),*/ + Scripts: rs.getVideoScripts(r.Context(), r, scene), + Subtitles: rs.getVideoSubtitles(r.Context(), r, scene), + Tags: rs.getVideoTags(r.Context(), r, scene), + Media: []HeresphereVideoMedia{}, + WriteFavorite: false, + WriteRating: false, + WriteTags: false, + WriteHSP: false, } - isFavorite := *scene.Rating > 85 - processedScene := heresphereVideoEntry{ - access: 1, - title: scene.Title, - description: scene.Details, - thumbnailImage: "", - thumbnailVideo: "", - dateReleased: scene.Date.Format("2006-02-01"), - duration: scene.PlayDuration, - rating: float32(*scene.Rating) * 0.05, // 0-5 - favorites: 0, - comments: scene.OCounter, - isFavorite: isFavorite, - projection: heresphereProjectionEquirectangular, - stereo: heresphereStereoMono, - isEyeSwapped: false, - fov: 180, - lens: heresphereLensLinear, - cameraIPD: 6.5, - hsp: fmt.Sprintf("/heresphere/%s/hsp", scene.ID), - eventServer: fmt.Sprintf("/heresphere/%s/event", scene.ID), - scripts: getVideoScripts(scene), - subtitles: getVideoSubtitles(scene), - tags: getVideoTags(scene), - media: getVideoMedia(scene), - writeFavorite: false, - writeRating: false, - writeTags: false, - writeHSP: false, + if user.NeedsMediaSource { + processedScene.Media = rs.getVideoMedia(r.Context(), r, scene) + } + if scene.Date != nil { + processedScene.DateReleased = scene.Date.Format("2006-01-02") + } + if scene.Rating != nil { + isFavorite := *scene.Rating > 85 + //processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 + processedScene.IsFavorite = isFavorite + } + primaryFile := scene.Files.Primary() + if primaryFile != nil { + processedScene.Duration = uint(handleFloat64Value(primaryFile.Duration)) } // Create a JSON encoder for the response writer - buf := &bytes.Buffer{} - encoder := json.NewEncoder(buf) - - // Write the JSON response - err = encoder.Encode(processedScene) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(processedScene) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - - utils.ServeStaticContent(w, r, buf.Bytes()) } -func (rs heresphereRoutes) heresphereLogin(w http.ResponseWriter, r *http.Request) { +func (rs heresphereRoutes) HeresphereLogin(w http.ResponseWriter, r *http.Request) { if r.Body == nil { http.Error(w, "Missing body", http.StatusBadRequest) return } - var user heresphereAuthReq + var user HeresphereAuthReq err := json.NewDecoder(r.Body).Decode(&user) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -491,16 +606,16 @@ func (rs heresphereRoutes) heresphereLogin(w http.ResponseWriter, r *http.Reques } // TODO: Auth - w.WriteHeader(http.StatusOK) + rs.HeresphereIndex(w, r) } -func (rs heresphereRoutes) heresphereLoginToken(w http.ResponseWriter, r *http.Request) { +func (rs heresphereRoutes) HeresphereLoginToken(w http.ResponseWriter, r *http.Request) { if r.Body == nil { http.Error(w, "Missing body", http.StatusBadRequest) return } - var user heresphereAuthReq + var user HeresphereAuthReq err := json.NewDecoder(r.Body).Decode(&user) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -509,25 +624,60 @@ func (rs heresphereRoutes) heresphereLoginToken(w http.ResponseWriter, r *http.R //TODO: Auth - auth := &heresphereAuthResp{ - auth_token: "yes", - access: heresphereMember, + //TODO: Will supply header auth-token in future requests, check it in other functions + auth := &HeresphereAuthResp{ + AuthToken: "yes", + Access: HeresphereMember, } // Create a JSON encoder for the response writer - buf, err := json.Marshal(auth) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(auth) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } +} - w.Header().Set("Content-Type", "application/json") - utils.ServeStaticContent(w, r, buf) +func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) + if err != nil { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + var scene *models.Scene + _ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + qb := rs.sceneFinder + scene, _ = qb.Find(ctx, sceneID) + + if scene != nil { + if err := scene.LoadPrimaryFile(ctx, rs.fileFinder); err != nil { + if !errors.Is(err, context.Canceled) { + logger.Errorf("error loading primary file for scene %d: %v", sceneID, err) + } + // set scene to nil so that it doesn't try to use the primary file + scene = nil + } + } + + return nil + }) + if scene == nil { + http.Error(w, http.StatusText(404), 404) + return + } + + ctx := context.WithValue(r.Context(), heresphereKey, scene) + next.ServeHTTP(w, r.WithContext(ctx)) + }) } func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("HereSphere-JSON-Version", strconv.Itoa(heresphereJsonVersion)) + w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} next.ServeHTTP(w, r) }) } diff --git a/internal/api/server.go b/internal/api/server.go index e1327c53617..940ba58f6e8 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -179,8 +179,10 @@ func Start() error { }.Routes()) r.Mount("/downloads", downloadsRoutes{}.Routes()) r.Mount("/heresphere", heresphereRoutes{ - txnManager: txnManager, - repository: txnManager, + txnManager: txnManager, + sceneFinder: txnManager.Scene, + fileFinder: txnManager.File, + repository: txnManager, }.Routes()) r.HandleFunc("/css", cssHandler(c, pluginCache)) From 1b79014be184f056de97276672d3dba3971a8b91 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 1 Jun 2023 18:27:47 +0200 Subject: [PATCH 003/144] Implemented various features in the heresphere api --- internal/api/routes_heresphere.go | 327 +++++++++++++++--------------- internal/api/server.go | 1 + 2 files changed, 168 insertions(+), 160 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 54aec07ad8a..0050e9b1036 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -73,14 +73,14 @@ type HeresphereIndexEntry struct { List []string `json:"list"` } type HeresphereIndex struct { - Access int `json:"access"` - //Banner HeresphereBanner `json:"banner"` + Access int `json:"access"` + Banner HeresphereBanner `json:"banner"` Library []HeresphereIndexEntry `json:"library"` } type HeresphereVideoScript struct { - Name string `json:"name"` - Url string `json:"url"` - //Rating float32 `json:"rating,omitempty"` + Name string `json:"name"` + Url string `json:"url"` + Rating float32 `json:"rating,omitempty"` // TODO: Floats become null, same with lists, should have default value instead // This is technically an api violation } @@ -111,20 +111,20 @@ type HeresphereVideoMedia struct { Sources []HeresphereVideoMediaSource `json:"sources"` } type HeresphereVideoEntry struct { - Access int `json:"access"` - Title string `json:"title"` - Description string `json:"description"` - ThumbnailImage string `json:"thumbnailImage"` - ThumbnailVideo string `json:"thumbnailVideo,omitempty"` - DateReleased string `json:"dateReleased"` - DateAdded string `json:"dateAdded"` - Duration uint `json:"duration,omitempty"` - /*Rating float32 `json:"rating,omitempty"` - Favorites float32 `json:"favorites,omitempty"` - Comments int `json:"comments"`*/ - IsFavorite bool `json:"isFavorite"` - Projection HeresphereProjection `json:"projection"` - Stereo HeresphereStereo `json:"stereo"` + Access int `json:"access"` + Title string `json:"title"` + Description string `json:"description"` + ThumbnailImage string `json:"thumbnailImage"` + ThumbnailVideo string `json:"thumbnailVideo,omitempty"` + DateReleased string `json:"dateReleased"` + DateAdded string `json:"dateAdded"` + Duration uint `json:"duration,omitempty"` + Rating float32 `json:"rating,omitempty"` + Favorites float32 `json:"favorites,omitempty"` + Comments int `json:"comments"` + IsFavorite bool `json:"isFavorite"` + Projection HeresphereProjection `json:"projection"` + Stereo HeresphereStereo `json:"stereo"` //IsEyeSwapped bool `json:"isEyeSwapped"` Fov float32 `json:"fov,omitempty"` Lens HeresphereLens `json:"lens"` @@ -141,16 +141,16 @@ type HeresphereVideoEntry struct { WriteHSP bool `json:"writeHSP"` } type HeresphereVideoEntryShort struct { - Link string `json:"link"` - Title string `json:"title"` - DateReleased string `json:"dateReleased"` - DateAdded string `json:"dateAdded"` - Duration uint `json:"duration,omitempty"` - /*Rating float32 `json:"rating,omitempty"` + Link string `json:"link"` + Title string `json:"title"` + DateReleased string `json:"dateReleased"` + DateAdded string `json:"dateAdded"` + Duration uint `json:"duration,omitempty"` + Rating float32 `json:"rating,omitempty"` Favorites int `json:"favorites"` - Comments int `json:"comments"`*/ - IsFavorite bool `json:"isFavorite"` - Tags []HeresphereVideoTag `json:"tags"` + Comments int `json:"comments"` + IsFavorite bool `json:"isFavorite"` + Tags []HeresphereVideoTag `json:"tags"` } type HeresphereVideoEntryUpdate struct { Username string `json:"username"` @@ -178,6 +178,7 @@ type heresphereRoutes struct { sceneFinder SceneFinder fileFinder file.Finder repository manager.Repository + resolver ResolverRoot } func (rs heresphereRoutes) Routes() chi.Router { @@ -190,6 +191,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Head("/", rs.HeresphereIndex) r.Post("/auth", rs.HeresphereLoginToken) + // TODO: /scan is damn slow r.Post("/scan", rs.HeresphereScan) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -210,7 +212,6 @@ func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.R // TODO: Auth } -// TODO: This probably isnt necessary func relUrlToAbs(r *http.Request, rel string) string { // Get the scheme (http or https) from the request scheme := "http" @@ -228,59 +229,68 @@ func relUrlToAbs(r *http.Request, rel string) string { func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoTag { processedTags := []HeresphereVideoTag{} - testTag := HeresphereVideoTag{ - Name: "Test:a", - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ + mark_ids, err := rs.resolver.Scene().SceneMarkers(ctx, scene) + if err == nil { + for _, mark := range mark_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Marker:%v", mark.Title), + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ + } + processedTags = append(processedTags, genTag) + } } - processedTags = append(processedTags, testTag) - if scene.LoadRelationships(ctx, rs.repository.Scene) == nil { - if scene.GalleryIDs.Loaded() { - for _, sceneTags := range scene.GalleryIDs.List() { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Gallery:%v", sceneTags), - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ - } - processedTags = append(processedTags, genTag) + gallery_ids, err := rs.resolver.Scene().Galleries(ctx, scene) + if err == nil { + for _, gal := range gallery_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Gallery:%v", gal.GetTitle()), + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ } + processedTags = append(processedTags, genTag) } + } - if scene.TagIDs.Loaded() { - for _, sceneTags := range scene.TagIDs.List() { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Tag:%v", sceneTags), - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ - } - processedTags = append(processedTags, genTag) + tag_ids, err := rs.resolver.Scene().Tags(ctx, scene) + if err == nil { + for _, tag := range tag_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Tag:%v", tag.Name), + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ } + processedTags = append(processedTags, genTag) } + } - if scene.PerformerIDs.Loaded() { - for _, sceneTags := range scene.PerformerIDs.List() { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Talent:%s", sceneTags), - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ - } - processedTags = append(processedTags, genTag) + perf_ids, err := rs.resolver.Scene().Performers(ctx, scene) + if err == nil { + for _, perf := range perf_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Talent:%s", perf.Name), + /*Start: 0.0, + End: 1.0, + Track: 0, + Rating: 0,*/ } + processedTags = append(processedTags, genTag) } + } - if scene.Movies.Loaded() { - for _, sceneTags := range scene.Movies.List() { + movie_ids, err := rs.resolver.Scene().Movies(ctx, scene) + if err == nil { + for _, movie := range movie_ids { + if movie.Movie != nil { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Movie:%v", sceneTags), + Name: fmt.Sprintf("Movie:%v", movie.Movie.Name), /*Start: 0.0, End: 1.0, Track: 0, @@ -289,25 +299,27 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc processedTags = append(processedTags, genTag) } } + } - if scene.StashIDs.Loaded() { - //TODO: Markers have timestamps? - for _, sceneTags := range scene.StashIDs.List() { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Scene:%v", sceneTags), - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ - } - processedTags = append(processedTags, genTag) + /*stash_ids, err := rs.resolver.Scene().StashIds(ctx, scene) + if err == nil { + //TODO: Markers have timestamps? + for _, stash := range stash_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Scene:%v", stash.), + //Start: 0.0, + //End: 1.0, + //Track: 0, + //Rating: 0, } + processedTags = append(processedTags, genTag) } - } + }*/ - if scene.StudioID != nil { + studio_id, err := rs.resolver.Scene().Studio(ctx, scene) + if err == nil && studio_id != nil { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Studio:%v", scene.StudioID), + Name: fmt.Sprintf("Studio:%v", studio_id.Name.String), /*Start: 0.0, End: 1.0, Track: 0, @@ -320,72 +332,64 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc } func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoScript { //TODO: Check if exists - processedScript := HeresphereVideoScript{ - Name: "Default script", - Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/funscript", scene.ID)), - //Rating: 2.5, + processedScripts := []HeresphereVideoScript{} + + exists, err := rs.resolver.Scene().Interactive(ctx, scene) + if err == nil && exists { + processedScript := HeresphereVideoScript{ + Name: "Default script", + Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/funscript", scene.ID)), + Rating: 4.2, + } + processedScripts = append(processedScripts, processedScript) } - processedScripts := []HeresphereVideoScript{processedScript} return processedScripts } func (rs heresphereRoutes) getVideoSubtitles(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { processedSubtitles := []HeresphereVideoSubtitle{} - //TODO: /scene/123/caption?lang=00&type=srt - - /*var captions []*models.VideoCaption - readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - var err error - primaryFile := s.Files.Primary() - if primaryFile == nil { - return nil + captions_id, err := rs.resolver.Scene().Captions(ctx, scene) + if err == nil { + for _, caption := range captions_id { + processedCaption := HeresphereVideoSubtitle{ + Name: caption.Filename, + Language: caption.LanguageCode, + Url: fmt.Sprintf("/scene/%v/caption?lang=%v&type=%v", scene.ID, caption.LanguageCode, caption.CaptionType), + } + processedSubtitles = append(processedSubtitles, processedCaption) } - - captions, err = rs.captionFinder.GetCaptions(ctx, primaryFile.Base().ID) - - return err - }) - if errors.Is(readTxnErr, context.Canceled) { - return - } - if readTxnErr != nil { - logger.Warnf("read transaction error on fetch scene captions: %v", readTxnErr) - http.Error(w, readTxnErr.Error(), http.StatusInternalServerError) - return } - for _, caption := range captions { - if lang != caption.LanguageCode || ext != caption.CaptionType { - continue - } - - sub, err := video.ReadSubs(caption.Path(s.Path)) - if err != nil { - logger.Warnf("error while reading subs: %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var buf bytes.Buffer - - err = sub.WriteToWebVTT(&buf) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "text/vtt") - utils.ServeStaticContent(w, r, buf.Bytes()) - return - }*/ - return processedSubtitles } func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoMedia { processedMedia := []HeresphereVideoMedia{} + mediaTypes := make(map[string][]HeresphereVideoMediaSource) + + file_ids, err := rs.resolver.Scene().Files(ctx, scene) + if err == nil { + for _, mediaFile := range file_ids { + processedEntry := HeresphereVideoMediaSource{ + Resolution: mediaFile.Height, + Height: mediaFile.Height, + Width: mediaFile.Width, + Size: mediaFile.Size, + Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/stream", scene.ID)), + } + mediaTypes[mediaFile.Format] = append(mediaTypes[mediaFile.Format], processedEntry) + } + } + + for codec, sources := range mediaTypes { + processedMedia = append(processedMedia, HeresphereVideoMedia{ + Name: codec, + Sources: sources, + }) + } + //TODO: Gather also secondary files, by Format, use a map/dict - mediaFile := scene.Files.Primary() + /*mediaFile := scene.Files.Primary() if mediaFile != nil { processedEntry := HeresphereVideoMediaSource{ Resolution: mediaFile.Height, @@ -399,17 +403,17 @@ func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, s Name: mediaFile.Format, Sources: processedSources, }) - } + }*/ //TODO: Transcode etc. /scene/%v/stream.mp4?resolution=ORIGINAL return processedMedia } func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { - /*banner := HeresphereBanner{ + banner := HeresphereBanner{ Image: relUrlToAbs(r, "/apple-touch-icon.png"), Link: relUrlToAbs(r, "/"), - }*/ + } var scenes []*models.Scene if err := rs.repository.WithTxn(r.Context(), func(ctx context.Context) error { @@ -431,8 +435,8 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques List: sceneUrls, } idx := HeresphereIndex{ - Access: HeresphereMember, - //Banner: banner, + Access: HeresphereMember, + Banner: banner, Library: []HeresphereIndexEntry{library}, } @@ -465,26 +469,25 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request Title: scene.GetTitle(), DateReleased: scene.CreatedAt.Format("2006-01-02"), DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60, - /*Rating: 2.5, + Duration: 0, + Rating: 0.0, Favorites: 0, - Comments: scene.OCounter,*/ - IsFavorite: false, - Tags: rs.getVideoTags(r.Context(), r, scene), + Comments: scene.OCounter, + IsFavorite: false, + Tags: rs.getVideoTags(r.Context(), r, scene), } if scene.Date != nil { processedScene.DateReleased = scene.Date.Format("2006-01-02") } if scene.Rating != nil { isFavorite := *scene.Rating > 85 - //processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 + processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 processedScene.IsFavorite = isFavorite } - //TODO: panic="relationship has not been loaded" - /*primaryFile := scene.Files.Primary() - if primaryFile != nil { - processedScene.Duration = handleFloat64Value(primaryFile.Duration) - }*/ + file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) + if err == nil && len(file_ids) > 0 { + processedScene.Duration = uint(handleFloat64Value(file_ids[0].Duration)) + } processedScenes = append(processedScenes, processedScene) } @@ -543,13 +546,13 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re ThumbnailVideo: relUrlToAbs(r, fmt.Sprintf("/scene/%v/preview", scene.ID)), DateReleased: scene.CreatedAt.Format("2006-01-02"), DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60.0, - /*Rating: 2.5, + Duration: 0, + Rating: 0.0, Favorites: 0, - Comments: scene.OCounter,*/ - IsFavorite: false, - Projection: HeresphereProjectionPerspective, // Default to flat cause i have no idea - Stereo: HeresphereStereoMono, // Default to flat cause i have no idea + Comments: scene.OCounter, + IsFavorite: false, + Projection: HeresphereProjectionPerspective, // Default to flat cause i have no idea + Stereo: HeresphereStereoMono, // Default to flat cause i have no idea //IsEyeSwapped: false, Fov: 180, Lens: HeresphereLensLinear, @@ -574,13 +577,17 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } if scene.Rating != nil { isFavorite := *scene.Rating > 85 - //processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 + processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 processedScene.IsFavorite = isFavorite } - primaryFile := scene.Files.Primary() + file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) + if err == nil && len(file_ids) > 0 { + processedScene.Duration = uint(handleFloat64Value(file_ids[0].Duration)) + } + /*primaryFile := scene.Files.Primary() if primaryFile != nil { processedScene.Duration = uint(handleFloat64Value(primaryFile.Duration)) - } + }*/ // Create a JSON encoder for the response writer w.Header().Set("Content-Type", "application/json") diff --git a/internal/api/server.go b/internal/api/server.go index 940ba58f6e8..be5bad8f027 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -183,6 +183,7 @@ func Start() error { sceneFinder: txnManager.Scene, fileFinder: txnManager.File, repository: txnManager, + resolver: resolver, }.Routes()) r.HandleFunc("/css", cssHandler(c, pluginCache)) From df1ad14158aee173d0c68252b3c3a2de06f38e36 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 1 Jun 2023 19:00:12 +0200 Subject: [PATCH 004/144] Update to follow spec more closely --- internal/api/routes_heresphere.go | 99 ++++++++++++------------------- 1 file changed, 39 insertions(+), 60 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 0050e9b1036..084d36b72cb 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -111,41 +111,41 @@ type HeresphereVideoMedia struct { Sources []HeresphereVideoMediaSource `json:"sources"` } type HeresphereVideoEntry struct { - Access int `json:"access"` - Title string `json:"title"` - Description string `json:"description"` - ThumbnailImage string `json:"thumbnailImage"` - ThumbnailVideo string `json:"thumbnailVideo,omitempty"` - DateReleased string `json:"dateReleased"` - DateAdded string `json:"dateAdded"` - Duration uint `json:"duration,omitempty"` - Rating float32 `json:"rating,omitempty"` - Favorites float32 `json:"favorites,omitempty"` - Comments int `json:"comments"` - IsFavorite bool `json:"isFavorite"` - Projection HeresphereProjection `json:"projection"` - Stereo HeresphereStereo `json:"stereo"` - //IsEyeSwapped bool `json:"isEyeSwapped"` - Fov float32 `json:"fov,omitempty"` - Lens HeresphereLens `json:"lens"` - //CameraIPD float32 `json:"cameraIPD,omitempty"` - /*Hsp string `json:"hsp,omitempty"` - EventServer string `json:"eventServer,omitempty"`*/ - Scripts []HeresphereVideoScript `json:"scripts,omitempty"` - Subtitles []HeresphereVideoSubtitle `json:"subtitles,omitempty"` - Tags []HeresphereVideoTag `json:"tags,omitempty"` - Media []HeresphereVideoMedia `json:"media,omitempty"` - WriteFavorite bool `json:"writeFavorite"` - WriteRating bool `json:"writeRating"` - WriteTags bool `json:"writeTags"` - WriteHSP bool `json:"writeHSP"` + Access int `json:"access"` + Title string `json:"title"` + Description string `json:"description"` + ThumbnailImage string `json:"thumbnailImage"` + ThumbnailVideo string `json:"thumbnailVideo,omitempty"` + DateReleased string `json:"dateReleased"` + DateAdded string `json:"dateAdded"` + Duration float64 `json:"duration,omitempty"` + Rating float32 `json:"rating,omitempty"` + Favorites float32 `json:"favorites,omitempty"` + Comments int `json:"comments"` + IsFavorite bool `json:"isFavorite"` + Projection HeresphereProjection `json:"projection"` + Stereo HeresphereStereo `json:"stereo"` + IsEyeSwapped bool `json:"isEyeSwapped"` + Fov float32 `json:"fov,omitempty"` + Lens HeresphereLens `json:"lens"` + CameraIPD float32 `json:"cameraIPD"` + Hsp string `json:"hsp,omitempty"` + EventServer string `json:"eventServer,omitempty"` + Scripts []HeresphereVideoScript `json:"scripts,omitempty"` + Subtitles []HeresphereVideoSubtitle `json:"subtitles,omitempty"` + Tags []HeresphereVideoTag `json:"tags,omitempty"` + Media []HeresphereVideoMedia `json:"media,omitempty"` + WriteFavorite bool `json:"writeFavorite"` + WriteRating bool `json:"writeRating"` + WriteTags bool `json:"writeTags"` + WriteHSP bool `json:"writeHSP"` } type HeresphereVideoEntryShort struct { Link string `json:"link"` Title string `json:"title"` DateReleased string `json:"dateReleased"` DateAdded string `json:"dateAdded"` - Duration uint `json:"duration,omitempty"` + Duration float64 `json:"duration,omitempty"` Rating float32 `json:"rating,omitempty"` Favorites int `json:"favorites"` Comments int `json:"comments"` @@ -387,23 +387,6 @@ func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, s Sources: sources, }) } - - //TODO: Gather also secondary files, by Format, use a map/dict - /*mediaFile := scene.Files.Primary() - if mediaFile != nil { - processedEntry := HeresphereVideoMediaSource{ - Resolution: mediaFile.Height, - Height: mediaFile.Height, - Width: mediaFile.Width, - Size: mediaFile.Size, - Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/stream", scene.ID)), - } - processedSources := []HeresphereVideoMediaSource{processedEntry} - processedMedia = append(processedMedia, HeresphereVideoMedia{ - Name: mediaFile.Format, - Sources: processedSources, - }) - }*/ //TODO: Transcode etc. /scene/%v/stream.mp4?resolution=ORIGINAL return processedMedia @@ -469,7 +452,7 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request Title: scene.GetTitle(), DateReleased: scene.CreatedAt.Format("2006-01-02"), DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 0, + Duration: 60.0, Rating: 0.0, Favorites: 0, Comments: scene.OCounter, @@ -486,7 +469,7 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request } file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) if err == nil && len(file_ids) > 0 { - processedScene.Duration = uint(handleFloat64Value(file_ids[0].Duration)) + processedScene.Duration = handleFloat64Value(file_ids[0].Duration) } processedScenes = append(processedScenes, processedScene) } @@ -509,7 +492,7 @@ func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Req func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) { //TODO: This //TODO: Auth - + w.WriteHeader(http.StatusNotImplemented) } func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { @@ -546,18 +529,18 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re ThumbnailVideo: relUrlToAbs(r, fmt.Sprintf("/scene/%v/preview", scene.ID)), DateReleased: scene.CreatedAt.Format("2006-01-02"), DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 0, + Duration: 60.0, Rating: 0.0, Favorites: 0, Comments: scene.OCounter, IsFavorite: false, Projection: HeresphereProjectionPerspective, // Default to flat cause i have no idea Stereo: HeresphereStereoMono, // Default to flat cause i have no idea - //IsEyeSwapped: false, - Fov: 180, - Lens: HeresphereLensLinear, - /*CameraIPD: 6.5, - Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp", scene.ID)), + IsEyeSwapped: false, + Fov: 180, + Lens: HeresphereLensLinear, + CameraIPD: 6.5, + /*Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp", scene.ID)), EventServer: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/event", scene.ID)),*/ Scripts: rs.getVideoScripts(r.Context(), r, scene), Subtitles: rs.getVideoSubtitles(r.Context(), r, scene), @@ -582,12 +565,8 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) if err == nil && len(file_ids) > 0 { - processedScene.Duration = uint(handleFloat64Value(file_ids[0].Duration)) + processedScene.Duration = handleFloat64Value(file_ids[0].Duration) } - /*primaryFile := scene.Files.Primary() - if primaryFile != nil { - processedScene.Duration = uint(handleFloat64Value(primaryFile.Duration)) - }*/ // Create a JSON encoder for the response writer w.Header().Set("Content-Type", "application/json") From 28604d96a57c01f75f2e995a39bedb1b0486d22b Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 1 Jun 2023 19:20:09 +0200 Subject: [PATCH 005/144] Added absolute path to captions --- internal/api/routes_heresphere.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 084d36b72cb..b95594a6f43 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -158,7 +158,7 @@ type HeresphereVideoEntryUpdate struct { IsFavorite bool `json:"isFavorite"` Rating float32 `json:"rating,omitempty"` Tags []HeresphereVideoTag `json:"tags"` - //In base64 + // In base64 Hsp string `json:"hsp"` DeleteFile bool `json:"deleteFile"` } @@ -198,7 +198,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Post("/", rs.HeresphereVideoData) r.Get("/", rs.HeresphereVideoData) - //Ours + r.Get("/hsp", rs.HeresphereVideoHsp) r.Post("/event", rs.HeresphereVideoEvent) }) @@ -303,14 +303,14 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc /*stash_ids, err := rs.resolver.Scene().StashIds(ctx, scene) if err == nil { - //TODO: Markers have timestamps? + // TODO: Markers have timestamps? for _, stash := range stash_ids { genTag := HeresphereVideoTag{ Name: fmt.Sprintf("Scene:%v", stash.), - //Start: 0.0, - //End: 1.0, - //Track: 0, - //Rating: 0, + // Start: 0.0, + // End: 1.0, + // Track: 0, + // Rating: 0, } processedTags = append(processedTags, genTag) } @@ -331,7 +331,7 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc return processedTags } func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoScript { - //TODO: Check if exists + // TODO: Check if exists processedScripts := []HeresphereVideoScript{} exists, err := rs.resolver.Scene().Interactive(ctx, scene) @@ -354,7 +354,7 @@ func (rs heresphereRoutes) getVideoSubtitles(ctx context.Context, r *http.Reques processedCaption := HeresphereVideoSubtitle{ Name: caption.Filename, Language: caption.LanguageCode, - Url: fmt.Sprintf("/scene/%v/caption?lang=%v&type=%v", scene.ID, caption.LanguageCode, caption.CaptionType), + Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/caption?lang=%v&type=%v", scene.ID, caption.LanguageCode, caption.CaptionType)), } processedSubtitles = append(processedSubtitles, processedCaption) } @@ -387,7 +387,7 @@ func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, s Sources: sources, }) } - //TODO: Transcode etc. /scene/%v/stream.mp4?resolution=ORIGINAL + // TODO: Transcode etc. /scene/%v/stream.mp4?resolution=ORIGINAL return processedMedia } @@ -432,7 +432,7 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques } } func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { - //TODO: Auth + // TODO: Auth var scenes []*models.Scene if err := rs.repository.WithTxn(r.Context(), func(ctx context.Context) error { var err error @@ -485,13 +485,13 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request } func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Request) { - //TODO: Auth + // TODO: Auth w.WriteHeader(http.StatusNotImplemented) } func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) { - //TODO: This - //TODO: Auth + // TODO: This + // TODO: Auth w.WriteHeader(http.StatusNotImplemented) } @@ -519,7 +519,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re return*/ } - //TODO: Auth + // TODO: Auth processedScene := HeresphereVideoEntry{ Access: HeresphereMember, From cd6a48a576e8ef3f1098659bf380c361de4f6c65 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 12:06:06 +0200 Subject: [PATCH 006/144] Added get multiple tags --- internal/api/routes_heresphere.go | 234 ++++++++++++++++++++++-------- 1 file changed, 174 insertions(+), 60 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index b95594a6f43..a36c51ca5db 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -9,7 +9,9 @@ import ( "strconv" "github.com/go-chi/chi" + "github.com/stashapp/stash/internal/api/loaders" "github.com/stashapp/stash/internal/manager" + "github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/pkg/file" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" @@ -139,6 +141,7 @@ type HeresphereVideoEntry struct { WriteRating bool `json:"writeRating"` WriteTags bool `json:"writeTags"` WriteHSP bool `json:"writeHSP"` + scene *models.Scene } type HeresphereVideoEntryShort struct { Link string `json:"link"` @@ -151,6 +154,7 @@ type HeresphereVideoEntryShort struct { Comments int `json:"comments"` IsFavorite bool `json:"isFavorite"` Tags []HeresphereVideoTag `json:"tags"` + scene *models.Scene } type HeresphereVideoEntryUpdate struct { Username string `json:"username"` @@ -191,7 +195,6 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Head("/", rs.HeresphereIndex) r.Post("/auth", rs.HeresphereLoginToken) - // TODO: /scan is damn slow r.Post("/scan", rs.HeresphereScan) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -222,22 +225,159 @@ func relUrlToAbs(r *http.Request, rel string) string { // Get the host from the request host := r.Host + // TODO: Support Forwarded standard + // Combine the scheme, host, and relative path to form the absolute URL return fmt.Sprintf("%s://%s%s", scheme, host, rel) } +func (rs heresphereRoutes) getVideosTags(ctx context.Context, vids []HeresphereVideoEntryShort) { + gallery_ids := make(map[int]*models.Gallery) + tag_ids := make(map[int]*models.Tag) + perf_ids := make(map[int]*models.Performer) + //stash_ids + + for _, vid := range vids { + vid.Tags = []HeresphereVideoTag{} + + if err := txn.WithReadTxn(ctx, rs.txnManager, func(ctx context.Context) error { + return vid.scene.LoadRelationships(ctx, rs.repository.Scene) + }); err != nil { + continue + } + + if vid.scene.GalleryIDs.Loaded() { + for _, id := range vid.scene.GalleryIDs.List() { + gallery_ids[id] = nil + } + } + if vid.scene.TagIDs.Loaded() { + for _, id := range vid.scene.TagIDs.List() { + tag_ids[id] = nil + } + } + if vid.scene.PerformerIDs.Loaded() { + for _, id := range vid.scene.PerformerIDs.List() { + perf_ids[id] = nil + } + } + + mark_ids, err := rs.resolver.Scene().SceneMarkers(ctx, vid.scene) + if err == nil { + for _, mark := range mark_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Marker:%v", mark.Title), + } + vid.Tags = append(vid.Tags, genTag) + } + } + + movie_ids, err := rs.resolver.Scene().Movies(ctx, vid.scene) + if err == nil { + for _, movie := range movie_ids { + if movie.Movie != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Movie:%v", movie.Movie.Name), + } + vid.Tags = append(vid.Tags, genTag) + } + } + } + + studio_id, err := rs.resolver.Scene().Studio(ctx, vid.scene) + if err == nil && studio_id != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Studio:%v", studio_id.Name.String), + } + vid.Tags = append(vid.Tags, genTag) + } + } + + /* + * TODO: DAMN this is ugly + * When you make the code uglier for the sake of performance + * I cri + * 😭 + */ + _gallery_ids := []int{} + _tag_ids := []int{} + _perf_ids := []int{} + for id, _ := range gallery_ids { + _gallery_ids = append(_gallery_ids, id) + } + for id, _ := range tag_ids { + _tag_ids = append(_tag_ids, id) + } + for id, _ := range perf_ids { + _perf_ids = append(_perf_ids, id) + } + + r_gallery_ids, errs := loaders.From(ctx).GalleryByID.LoadAll(_gallery_ids) + if firstError(errs) != nil { + return + } + r_tag_ids, errs := loaders.From(ctx).TagByID.LoadAll(_tag_ids) + if firstError(errs) != nil { + return + } + r_perf_ids, errs := loaders.From(ctx).PerformerByID.LoadAll(_perf_ids) + if firstError(errs) != nil { + return + } + + for idx, obj := range r_gallery_ids { + gallery_ids[idx] = obj + } + for idx, obj := range r_tag_ids { + tag_ids[idx] = obj + } + for idx, obj := range r_perf_ids { + perf_ids[idx] = obj + } + + for _, vid := range vids { + for _, id := range vid.scene.GalleryIDs.List() { + if gallery_ids[id] != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Gallery:%v", gallery_ids[id].GetTitle()), + } + vid.Tags = append(vid.Tags, genTag) + } + } + for _, id := range vid.scene.TagIDs.List() { + if tag_ids[id] != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Tag:%v", tag_ids[id].Name), + } + vid.Tags = append(vid.Tags, genTag) + } + } + for _, id := range vid.scene.GalleryIDs.List() { + if perf_ids[id] != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Talent:%v", perf_ids[id].Name), + } + vid.Tags = append(vid.Tags, genTag) + } + } + } +} + +// TODO: Consolidate into one func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoTag { processedTags := []HeresphereVideoTag{} + if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + return scene.LoadRelationships(ctx, rs.repository.Scene) + }); err != nil { + return processedTags + } + mark_ids, err := rs.resolver.Scene().SceneMarkers(ctx, scene) if err == nil { for _, mark := range mark_ids { genTag := HeresphereVideoTag{ Name: fmt.Sprintf("Marker:%v", mark.Title), - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ } processedTags = append(processedTags, genTag) } @@ -248,10 +388,6 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc for _, gal := range gallery_ids { genTag := HeresphereVideoTag{ Name: fmt.Sprintf("Gallery:%v", gal.GetTitle()), - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ } processedTags = append(processedTags, genTag) } @@ -262,10 +398,6 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc for _, tag := range tag_ids { genTag := HeresphereVideoTag{ Name: fmt.Sprintf("Tag:%v", tag.Name), - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ } processedTags = append(processedTags, genTag) } @@ -276,10 +408,6 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc for _, perf := range perf_ids { genTag := HeresphereVideoTag{ Name: fmt.Sprintf("Talent:%s", perf.Name), - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ } processedTags = append(processedTags, genTag) } @@ -291,45 +419,25 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc if movie.Movie != nil { genTag := HeresphereVideoTag{ Name: fmt.Sprintf("Movie:%v", movie.Movie.Name), - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ } processedTags = append(processedTags, genTag) } } } - /*stash_ids, err := rs.resolver.Scene().StashIds(ctx, scene) - if err == nil { - // TODO: Markers have timestamps? - for _, stash := range stash_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Scene:%v", stash.), - // Start: 0.0, - // End: 1.0, - // Track: 0, - // Rating: 0, - } - processedTags = append(processedTags, genTag) - } - }*/ + //stash_ids, err := rs.resolver.Scene().StashIds(ctx, scene) studio_id, err := rs.resolver.Scene().Studio(ctx, scene) if err == nil && studio_id != nil { genTag := HeresphereVideoTag{ Name: fmt.Sprintf("Studio:%v", studio_id.Name.String), - /*Start: 0.0, - End: 1.0, - Track: 0, - Rating: 0,*/ } processedTags = append(processedTags, genTag) } return processedTags } + func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoScript { // TODO: Check if exists processedScripts := []HeresphereVideoScript{} @@ -348,6 +456,8 @@ func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, func (rs heresphereRoutes) getVideoSubtitles(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { processedSubtitles := []HeresphereVideoSubtitle{} + // TODO: Use sceneResolver Paths => rs.resolver.Scene().Paths() + captions_id, err := rs.resolver.Scene().Captions(ctx, scene) if err == nil { for _, caption := range captions_id { @@ -408,9 +518,9 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques return } - var sceneUrls []string - for _, scene := range scenes { - sceneUrls = append(sceneUrls, relUrlToAbs(r, fmt.Sprintf("/heresphere/%v", scene.ID))) + sceneUrls := make([]string, len(scenes)) + for idx, scene := range scenes { + sceneUrls[idx] = relUrlToAbs(r, fmt.Sprintf("/heresphere/%v", scene.ID)) } library := HeresphereIndexEntry{ @@ -443,21 +553,23 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request return } + // TODO: /scan is damn slow // Processing each scene and creating a new list - var processedScenes []HeresphereVideoEntryShort - for _, scene := range scenes { + processedScenes := make([]HeresphereVideoEntryShort, len(scenes)) + for idx, scene := range scenes { // Perform the necessary processing on each scene processedScene := HeresphereVideoEntryShort{ Link: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v", scene.ID)), Title: scene.GetTitle(), DateReleased: scene.CreatedAt.Format("2006-01-02"), DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60.0, + Duration: 60000.0, Rating: 0.0, Favorites: 0, Comments: scene.OCounter, IsFavorite: false, - Tags: rs.getVideoTags(r.Context(), r, scene), + //Tags: rs.getVideoTags(r.Context(), r, scene), + scene: scene, } if scene.Date != nil { processedScene.DateReleased = scene.Date.Format("2006-01-02") @@ -469,10 +581,11 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request } file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) if err == nil && len(file_ids) > 0 { - processedScene.Duration = handleFloat64Value(file_ids[0].Duration) + processedScene.Duration = handleFloat64Value(file_ids[0].Duration * 1000.0) } - processedScenes = append(processedScenes, processedScene) + processedScenes[idx] = processedScene } + rs.getVideosTags(r.Context(), processedScenes) // Create a JSON encoder for the response writer w.Header().Set("Content-Type", "application/json") @@ -529,7 +642,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re ThumbnailVideo: relUrlToAbs(r, fmt.Sprintf("/scene/%v/preview", scene.ID)), DateReleased: scene.CreatedAt.Format("2006-01-02"), DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60.0, + Duration: 60000.0, Rating: 0.0, Favorites: 0, Comments: scene.OCounter, @@ -550,7 +663,9 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re WriteRating: false, WriteTags: false, WriteHSP: false, + scene: scene, } + //rs.getVideosTags(r.Context(), []HeresphereVideoEntry{processedScene}) if user.NeedsMediaSource { processedScene.Media = rs.getVideoMedia(r.Context(), r, scene) @@ -565,7 +680,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) if err == nil && len(file_ids) > 0 { - processedScene.Duration = handleFloat64Value(file_ids[0].Duration) + processedScene.Duration = handleFloat64Value(file_ids[0].Duration * 1000.0) } // Create a JSON encoder for the response writer @@ -579,11 +694,6 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } func (rs heresphereRoutes) HeresphereLogin(w http.ResponseWriter, r *http.Request) { - if r.Body == nil { - http.Error(w, "Missing body", http.StatusBadRequest) - return - } - var user HeresphereAuthReq err := json.NewDecoder(r.Body).Decode(&user) if err != nil { @@ -591,16 +701,20 @@ func (rs heresphereRoutes) HeresphereLogin(w http.ResponseWriter, r *http.Reques return } + if config.GetInstance().HasCredentials() { + /*err := manager.GetInstance().SessionStore.Login(w, r) + if err != nil { + // always log the error + logger.Errorf("Error logging in: %v", err) + }*/ + + } + // TODO: Auth rs.HeresphereIndex(w, r) } func (rs heresphereRoutes) HeresphereLoginToken(w http.ResponseWriter, r *http.Request) { - if r.Body == nil { - http.Error(w, "Missing body", http.StatusBadRequest) - return - } - var user HeresphereAuthReq err := json.NewDecoder(r.Body).Decode(&user) if err != nil { From 699033ee748afa9f7c838428d34e77f396e07e1f Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 14:36:52 +0200 Subject: [PATCH 007/144] Working authentication --- internal/api/authentication.go | 2 +- internal/api/context_keys.go | 1 + internal/api/routes_heresphere.go | 324 +++++++++--------------------- pkg/session/session.go | 7 + 4 files changed, 109 insertions(+), 225 deletions(-) diff --git a/internal/api/authentication.go b/internal/api/authentication.go index 94b5328f5bf..2295029b1c8 100644 --- a/internal/api/authentication.go +++ b/internal/api/authentication.go @@ -26,7 +26,7 @@ const ( func allowUnauthenticated(r *http.Request) bool { // #2715 - allow access to UI files - return strings.HasPrefix(r.URL.Path, loginEndpoint) || r.URL.Path == logoutEndpoint || r.URL.Path == "/css" || strings.HasPrefix(r.URL.Path, "/assets") + return strings.HasPrefix(r.URL.Path, loginEndpoint) || r.URL.Path == logoutEndpoint || r.URL.Path == "/css" || strings.HasPrefix(r.URL.Path, "/assets") || strings.HasPrefix(r.URL.Path, "/heresphere") } func authenticateHandler() func(http.Handler) http.Handler { diff --git a/internal/api/context_keys.go b/internal/api/context_keys.go index 191370a5d49..59e54bc6704 100644 --- a/internal/api/context_keys.go +++ b/internal/api/context_keys.go @@ -14,4 +14,5 @@ const ( downloadKey imageKey heresphereKey + heresphereUserKey ) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index a36c51ca5db..43d7ee99377 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -7,9 +7,9 @@ import ( "fmt" "net/http" "strconv" + "strings" "github.com/go-chi/chi" - "github.com/stashapp/stash/internal/api/loaders" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/pkg/file" @@ -56,11 +56,6 @@ const ( HeresphereLensVRCA220 HeresphereLens = "VRCA220" ) -type HeresphereAuthReq struct { - Username string `json:"username"` - Password string `json:"password"` - NeedsMediaSource bool `json:"needsMediaSource,omitempty"` -} type HeresphereAuthResp struct { AuthToken string `json:"auth-token"` Access int `json:"access"` @@ -141,7 +136,6 @@ type HeresphereVideoEntry struct { WriteRating bool `json:"writeRating"` WriteTags bool `json:"writeTags"` WriteHSP bool `json:"writeHSP"` - scene *models.Scene } type HeresphereVideoEntryShort struct { Link string `json:"link"` @@ -154,17 +148,17 @@ type HeresphereVideoEntryShort struct { Comments int `json:"comments"` IsFavorite bool `json:"isFavorite"` Tags []HeresphereVideoTag `json:"tags"` - scene *models.Scene } -type HeresphereVideoEntryUpdate struct { - Username string `json:"username"` - Password string `json:"password"` - IsFavorite bool `json:"isFavorite"` - Rating float32 `json:"rating,omitempty"` - Tags []HeresphereVideoTag `json:"tags"` +type HeresphereAuthReq struct { + Username string `json:"username"` + Password string `json:"password"` + NeedsMediaSource bool `json:"needsMediaSource,omitempty"` + IsFavorite bool `json:"isFavorite,omitempty"` + Rating float32 `json:"rating,omitempty"` + Tags []HeresphereVideoTag `json:"tags,omitempty"` // In base64 - Hsp string `json:"hsp"` - DeleteFile bool `json:"deleteFile"` + Hsp string `json:"hsp,omitempty"` + DeleteFile bool `json:"deleteFile,omitempty"` } type HeresphereVideoEvent struct { Username string `json:"username"` @@ -190,12 +184,13 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Route("/", func(r chi.Router) { r.Use(rs.HeresphereCtx) - r.Post("/", rs.HeresphereLogin) + r.Post("/", rs.HeresphereIndex) r.Get("/", rs.HeresphereIndex) r.Head("/", rs.HeresphereIndex) r.Post("/auth", rs.HeresphereLoginToken) - r.Post("/scan", rs.HeresphereScan) + //r.Post("/scan", rs.HeresphereScan) + r.Post("/event", rs.HeresphereVideoEvent) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -203,18 +198,12 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Get("/", rs.HeresphereVideoData) r.Get("/hsp", rs.HeresphereVideoHsp) - r.Post("/event", rs.HeresphereVideoEvent) }) }) return r } -func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - // TODO: Auth -} - func relUrlToAbs(r *http.Request, rel string) string { // Get the scheme (http or https) from the request scheme := "http" @@ -231,139 +220,26 @@ func relUrlToAbs(r *http.Request, rel string) string { return fmt.Sprintf("%s://%s%s", scheme, host, rel) } -func (rs heresphereRoutes) getVideosTags(ctx context.Context, vids []HeresphereVideoEntryShort) { - gallery_ids := make(map[int]*models.Gallery) - tag_ids := make(map[int]*models.Tag) - perf_ids := make(map[int]*models.Performer) - //stash_ids - - for _, vid := range vids { - vid.Tags = []HeresphereVideoTag{} - - if err := txn.WithReadTxn(ctx, rs.txnManager, func(ctx context.Context) error { - return vid.scene.LoadRelationships(ctx, rs.repository.Scene) - }); err != nil { - continue - } - - if vid.scene.GalleryIDs.Loaded() { - for _, id := range vid.scene.GalleryIDs.List() { - gallery_ids[id] = nil - } - } - if vid.scene.TagIDs.Loaded() { - for _, id := range vid.scene.TagIDs.List() { - tag_ids[id] = nil - } - } - if vid.scene.PerformerIDs.Loaded() { - for _, id := range vid.scene.PerformerIDs.List() { - perf_ids[id] = nil - } - } - - mark_ids, err := rs.resolver.Scene().SceneMarkers(ctx, vid.scene) - if err == nil { - for _, mark := range mark_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Marker:%v", mark.Title), - } - vid.Tags = append(vid.Tags, genTag) - } - } - - movie_ids, err := rs.resolver.Scene().Movies(ctx, vid.scene) - if err == nil { - for _, movie := range movie_ids { - if movie.Movie != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Movie:%v", movie.Movie.Name), - } - vid.Tags = append(vid.Tags, genTag) - } - } - } - - studio_id, err := rs.resolver.Scene().Studio(ctx, vid.scene) - if err == nil && studio_id != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Studio:%v", studio_id.Name.String), - } - vid.Tags = append(vid.Tags, genTag) - } - } +func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { + // TODO: This - /* - * TODO: DAMN this is ugly - * When you make the code uglier for the sake of performance - * I cri - * 😭 - */ - _gallery_ids := []int{} - _tag_ids := []int{} - _perf_ids := []int{} - for id, _ := range gallery_ids { - _gallery_ids = append(_gallery_ids, id) - } - for id, _ := range tag_ids { - _tag_ids = append(_tag_ids, id) - } - for id, _ := range perf_ids { - _perf_ids = append(_perf_ids, id) - } + // Only accessible with auth-key + // Seems we need to trust the Username, or get a user specific auth key + // To have any sort of safety on this endpoint - r_gallery_ids, errs := loaders.From(ctx).GalleryByID.LoadAll(_gallery_ids) - if firstError(errs) != nil { - return - } - r_tag_ids, errs := loaders.From(ctx).TagByID.LoadAll(_tag_ids) - if firstError(errs) != nil { - return - } - r_perf_ids, errs := loaders.From(ctx).PerformerByID.LoadAll(_perf_ids) - if firstError(errs) != nil { - return - } + w.WriteHeader(http.StatusNotImplemented) +} - for idx, obj := range r_gallery_ids { - gallery_ids[idx] = obj - } - for idx, obj := range r_tag_ids { - tag_ids[idx] = obj - } - for idx, obj := range r_perf_ids { - perf_ids[idx] = obj - } +func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Request) { + // TODO: This + w.WriteHeader(http.StatusNotImplemented) +} - for _, vid := range vids { - for _, id := range vid.scene.GalleryIDs.List() { - if gallery_ids[id] != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Gallery:%v", gallery_ids[id].GetTitle()), - } - vid.Tags = append(vid.Tags, genTag) - } - } - for _, id := range vid.scene.TagIDs.List() { - if tag_ids[id] != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Tag:%v", tag_ids[id].Name), - } - vid.Tags = append(vid.Tags, genTag) - } - } - for _, id := range vid.scene.GalleryIDs.List() { - if perf_ids[id] != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Talent:%v", perf_ids[id].Name), - } - vid.Tags = append(vid.Tags, genTag) - } - } - } +func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) { + // TODO: This + w.WriteHeader(http.StatusNotImplemented) } -// TODO: Consolidate into one func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoTag { processedTags := []HeresphereVideoTag{} @@ -439,14 +315,13 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc } func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoScript { - // TODO: Check if exists processedScripts := []HeresphereVideoScript{} exists, err := rs.resolver.Scene().Interactive(ctx, scene) if err == nil && exists { processedScript := HeresphereVideoScript{ Name: "Default script", - Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/funscript", scene.ID)), + Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/funscript?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), Rating: 4.2, } processedScripts = append(processedScripts, processedScript) @@ -456,15 +331,13 @@ func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, func (rs heresphereRoutes) getVideoSubtitles(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { processedSubtitles := []HeresphereVideoSubtitle{} - // TODO: Use sceneResolver Paths => rs.resolver.Scene().Paths() - captions_id, err := rs.resolver.Scene().Captions(ctx, scene) if err == nil { for _, caption := range captions_id { processedCaption := HeresphereVideoSubtitle{ Name: caption.Filename, Language: caption.LanguageCode, - Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/caption?lang=%v&type=%v", scene.ID, caption.LanguageCode, caption.CaptionType)), + Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/caption?lang=%v&type=%v&apikey=%v", scene.ID, caption.LanguageCode, caption.CaptionType, config.GetInstance().GetAPIKey())), } processedSubtitles = append(processedSubtitles, processedCaption) } @@ -485,7 +358,7 @@ func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, s Height: mediaFile.Height, Width: mediaFile.Width, Size: mediaFile.Size, - Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/stream", scene.ID)), + Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/stream?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), } mediaTypes[mediaFile.Format] = append(mediaTypes[mediaFile.Format], processedEntry) } @@ -541,8 +414,9 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques return } } -func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { - // TODO: Auth + +// TODO: Consider removing, manual scan is faster +/*func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { var scenes []*models.Scene if err := rs.repository.WithTxn(r.Context(), func(ctx context.Context) error { var err error @@ -568,8 +442,7 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request Favorites: 0, Comments: scene.OCounter, IsFavorite: false, - //Tags: rs.getVideoTags(r.Context(), r, scene), - scene: scene, + Tags: rs.getVideoTags(r.Context(), r, scene), } if scene.Date != nil { processedScene.DateReleased = scene.Date.Format("2006-01-02") @@ -585,7 +458,6 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request } processedScenes[idx] = processedScene } - rs.getVideosTags(r.Context(), processedScenes) // Create a JSON encoder for the response writer w.Header().Set("Content-Type", "application/json") @@ -595,51 +467,26 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request http.Error(w, err.Error(), http.StatusInternalServerError) return } -} - -func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Request) { - // TODO: Auth - w.WriteHeader(http.StatusNotImplemented) -} - -func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) { - // TODO: This - // TODO: Auth - w.WriteHeader(http.StatusNotImplemented) -} +}*/ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { - scene := r.Context().Value(heresphereKey).(*models.Scene) - // This endpoint can receive 2 types of requests // One is a video request (HeresphereAuthReq) // Other is an update (HeresphereVideoEntryUpdate) - user := HeresphereAuthReq{ - NeedsMediaSource: true, - } - userupd := HeresphereVideoEntryUpdate{} - - err := json.NewDecoder(r.Body).Decode(&user) - if err != nil { - err = json.NewDecoder(r.Body).Decode(&userupd) - if err == nil { - rs.HeresphereVideoDataUpdate(w, r) - return - } - - /*http.Error(w, err.Error(), http.StatusBadRequest) - return*/ + user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) + if user.Tags != nil { + rs.HeresphereVideoDataUpdate(w, r) + return } - // TODO: Auth - + scene := r.Context().Value(heresphereKey).(*models.Scene) processedScene := HeresphereVideoEntry{ Access: HeresphereMember, Title: scene.GetTitle(), Description: scene.Details, - ThumbnailImage: relUrlToAbs(r, fmt.Sprintf("/scene/%v/screenshot", scene.ID)), - ThumbnailVideo: relUrlToAbs(r, fmt.Sprintf("/scene/%v/preview", scene.ID)), + ThumbnailImage: relUrlToAbs(r, fmt.Sprintf("/scene/%v/screenshot?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), + ThumbnailVideo: relUrlToAbs(r, fmt.Sprintf("/scene/%v/preview?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), DateReleased: scene.CreatedAt.Format("2006-01-02"), DateAdded: scene.CreatedAt.Format("2006-01-02"), Duration: 60000.0, @@ -654,7 +501,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Lens: HeresphereLensLinear, CameraIPD: 6.5, /*Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp", scene.ID)), - EventServer: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/event", scene.ID)),*/ + EventServer: relUrlToAbs(r, "/heresphere/event"),*/ Scripts: rs.getVideoScripts(r.Context(), r, scene), Subtitles: rs.getVideoSubtitles(r.Context(), r, scene), Tags: rs.getVideoTags(r.Context(), r, scene), @@ -663,9 +510,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re WriteRating: false, WriteTags: false, WriteHSP: false, - scene: scene, } - //rs.getVideosTags(r.Context(), []HeresphereVideoEntry{processedScene}) if user.NeedsMediaSource { processedScene.Media = rs.getVideoMedia(r.Context(), r, scene) @@ -693,53 +538,45 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } } -func (rs heresphereRoutes) HeresphereLogin(w http.ResponseWriter, r *http.Request) { - var user HeresphereAuthReq - err := json.NewDecoder(r.Body).Decode(&user) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - +func basicLogin(username string, password string) bool { if config.GetInstance().HasCredentials() { - /*err := manager.GetInstance().SessionStore.Login(w, r) - if err != nil { - // always log the error - logger.Errorf("Error logging in: %v", err) - }*/ - + err := manager.GetInstance().SessionStore.LoginPlain(username, password) + return err != nil } - - // TODO: Auth - rs.HeresphereIndex(w, r) + return false } func (rs heresphereRoutes) HeresphereLoginToken(w http.ResponseWriter, r *http.Request) { - var user HeresphereAuthReq - err := json.NewDecoder(r.Body).Decode(&user) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) + user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) + + if basicLogin(user.Username, user.Password) { + writeNotAuthorized(w, r, "Invalid credentials") return } - //TODO: Auth + key := config.GetInstance().GetAPIKey() + if len(key) == 0 { + writeNotAuthorized(w, r, "Missing auth key!") + return + } - //TODO: Will supply header auth-token in future requests, check it in other functions auth := &HeresphereAuthResp{ - AuthToken: "yes", + AuthToken: key, Access: HeresphereMember, } // Create a JSON encoder for the response writer w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - err = json.NewEncoder(w).Encode(auth) + err := json.NewEncoder(w).Encode(auth) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } +// TODO: This is a copy of the Ctx from routes_scene +// Create a general version func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) @@ -775,9 +612,48 @@ func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { }) } +func HeresphereHasValidToken(r *http.Request) bool { + return len(r.Header.Get("auth-token")) > 0 && r.Header.Get("auth-token") == config.GetInstance().GetAPIKey() +} + +func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { + banner := HeresphereBanner{ + Image: relUrlToAbs(r, "/apple-touch-icon.png"), + Link: relUrlToAbs(r, "/"), + } + library := HeresphereIndexEntry{ + Name: msg, + } + idx := HeresphereIndex{ + Access: HeresphereBadLogin, + Banner: banner, + Library: []HeresphereIndexEntry{library}, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err := json.NewEncoder(w).Encode(idx) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} - next.ServeHTTP(w, r) + + user := HeresphereAuthReq{NeedsMediaSource: true} + json.NewDecoder(r.Body).Decode(&user) + + if config.GetInstance().HasCredentials() && + !HeresphereHasValidToken(r) && + !strings.HasPrefix(r.URL.Path, "/heresphere/auth") { + writeNotAuthorized(w, r, "Unauthorized!") + return + } + + ctx := context.WithValue(r.Context(), heresphereUserKey, user) + next.ServeHTTP(w, r.WithContext(ctx)) }) } diff --git a/pkg/session/session.go b/pkg/session/session.go index 9fcb875490f..3e266882339 100644 --- a/pkg/session/session.go +++ b/pkg/session/session.go @@ -62,6 +62,13 @@ func NewStore(c SessionConfig) *Store { return ret } +func (s *Store) LoginPlain(username string, password string) error { + if !s.config.ValidateCredentials(username, password) { + return &InvalidCredentialsError{Username: username} + } + return nil +} + func (s *Store) Login(w http.ResponseWriter, r *http.Request) error { // ignore error - we want a new session regardless newSession, _ := s.sessionStore.Get(r, cookieName) From a784f6cc30f8cbfe03175cf7a138da941ef20594 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:14:20 +0200 Subject: [PATCH 008/144] Added vr projection detection --- internal/api/routes_heresphere.go | 64 ++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 43d7ee99377..70e03f5b102 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -469,6 +469,57 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques } }*/ +func FindProjection(path string) (proj HeresphereProjection, stereo HeresphereStereo, eyeswap bool, fov float32, lens HeresphereLens, ipd float32) { + proj, stereo, eyeswap, fov, lens, ipd = HeresphereProjectionPerspective, HeresphereStereoMono, false, 180, HeresphereLensLinear, 6.5 + + path = strings.ToUpper(path) + + if strings.Index(path, "_LR") != -1 || strings.Index(path, "_3DH") != -1 { + stereo = HeresphereStereoSbs + } + if strings.Index(path, "_RL") != -1 { + stereo = HeresphereStereoSbs + eyeswap = true + } + if strings.Index(path, "_TB") != -1 || strings.Index(path, "_3DV") != -1 { + stereo = HeresphereStereoTB + } + if strings.Index(path, "_BT") != -1 { + stereo = HeresphereStereoTB + eyeswap = true + } + + if strings.Index(path, "_EAC360") != -1 || strings.Index(path, "_360EAC") != -1 { + proj = HeresphereProjectionEquirectangularCubemap + } + if strings.Index(path, "_360") != -1 { + proj = HeresphereProjectionEquirectangular360 + } + if strings.Index(path, "_F180") != -1 || strings.Index(path, "_180F") != -1 { + proj = HeresphereProjectionFisheye + } else if strings.Index(path, "_180") != -1 { + proj = HeresphereProjectionEquirectangular + } + if strings.Index(path, "_MKX200") != -1 { + proj = HeresphereProjectionFisheye + fov = 200 + } + if strings.Index(path, "_MKX220") != -1 { + proj = HeresphereProjectionFisheye + fov = 220 + } + if strings.Index(path, "_RF52") != -1 { + proj = HeresphereProjectionFisheye + fov = 190 + } + if strings.Index(path, "_VRCA220") != -1 { + proj = HeresphereProjectionFisheye + fov = 220 + } + + return +} + func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { // This endpoint can receive 2 types of requests // One is a video request (HeresphereAuthReq) @@ -481,6 +532,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } scene := r.Context().Value(heresphereKey).(*models.Scene) + proj, stereo, isEyeSwapped, fov, lens, ipd := FindProjection(scene.Files.Primary().Basename) processedScene := HeresphereVideoEntry{ Access: HeresphereMember, Title: scene.GetTitle(), @@ -494,12 +546,12 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Favorites: 0, Comments: scene.OCounter, IsFavorite: false, - Projection: HeresphereProjectionPerspective, // Default to flat cause i have no idea - Stereo: HeresphereStereoMono, // Default to flat cause i have no idea - IsEyeSwapped: false, - Fov: 180, - Lens: HeresphereLensLinear, - CameraIPD: 6.5, + Projection: proj, + Stereo: stereo, + IsEyeSwapped: isEyeSwapped, + Fov: fov, + Lens: lens, + CameraIPD: ipd, /*Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp", scene.ID)), EventServer: relUrlToAbs(r, "/heresphere/event"),*/ Scripts: rs.getVideoScripts(r.Context(), r, scene), From 50a172f44f334e8f5d561f868e0d185e81c195e0 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:25:51 +0200 Subject: [PATCH 009/144] The linting update --- internal/api/routes_heresphere.go | 37 ++++++++++++++++++------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 70e03f5b102..f1ac853c49d 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -189,7 +189,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Head("/", rs.HeresphereIndex) r.Post("/auth", rs.HeresphereLoginToken) - //r.Post("/scan", rs.HeresphereScan) + // r.Post("/scan", rs.HeresphereScan) r.Post("/event", rs.HeresphereVideoEvent) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -301,7 +301,7 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc } } - //stash_ids, err := rs.resolver.Scene().StashIds(ctx, scene) + // stash_ids, err := rs.resolver.Scene().StashIds(ctx, scene) studio_id, err := rs.resolver.Scene().Studio(ctx, scene) if err == nil && studio_id != nil { @@ -473,48 +473,52 @@ func FindProjection(path string) (proj HeresphereProjection, stereo HeresphereSt proj, stereo, eyeswap, fov, lens, ipd = HeresphereProjectionPerspective, HeresphereStereoMono, false, 180, HeresphereLensLinear, 6.5 path = strings.ToUpper(path) + // TODO: Detect video tags - if strings.Index(path, "_LR") != -1 || strings.Index(path, "_3DH") != -1 { + if strings.Contains(path, "_LR") || strings.Contains(path, "_3DH") { stereo = HeresphereStereoSbs } - if strings.Index(path, "_RL") != -1 { + if strings.Contains(path, "_RL") { stereo = HeresphereStereoSbs eyeswap = true } - if strings.Index(path, "_TB") != -1 || strings.Index(path, "_3DV") != -1 { + if strings.Contains(path, "_TB") || strings.Contains(path, "_3DV") { stereo = HeresphereStereoTB } - if strings.Index(path, "_BT") != -1 { + if strings.Contains(path, "_BT") { stereo = HeresphereStereoTB eyeswap = true } - if strings.Index(path, "_EAC360") != -1 || strings.Index(path, "_360EAC") != -1 { + if strings.Contains(path, "_EAC360") || strings.Contains(path, "_360EAC") { proj = HeresphereProjectionEquirectangularCubemap } - if strings.Index(path, "_360") != -1 { + if strings.Contains(path, "_360") { proj = HeresphereProjectionEquirectangular360 } - if strings.Index(path, "_F180") != -1 || strings.Index(path, "_180F") != -1 { + if strings.Contains(path, "_F180") || strings.Contains(path, "_180F") || strings.Contains(path, "_VR180") { proj = HeresphereProjectionFisheye - } else if strings.Index(path, "_180") != -1 { + } else if strings.Contains(path, "_180") { proj = HeresphereProjectionEquirectangular } - if strings.Index(path, "_MKX200") != -1 { + if strings.Contains(path, "_MKX200") { proj = HeresphereProjectionFisheye fov = 200 + lens = HeresphereLensMKX200 } - if strings.Index(path, "_MKX220") != -1 { + if strings.Contains(path, "_MKX220") { proj = HeresphereProjectionFisheye fov = 220 + lens = HeresphereLensMKX220 } - if strings.Index(path, "_RF52") != -1 { + if strings.Contains(path, "_RF52") { proj = HeresphereProjectionFisheye fov = 190 } - if strings.Index(path, "_VRCA220") != -1 { + if strings.Contains(path, "_VRCA220") { proj = HeresphereProjectionFisheye fov = 220 + lens = HeresphereLensVRCA220 } return @@ -696,7 +700,10 @@ func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} user := HeresphereAuthReq{NeedsMediaSource: true} - json.NewDecoder(r.Body).Decode(&user) + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } if config.GetInstance().HasCredentials() && !HeresphereHasValidToken(r) && From ff4d2629bc2b15556765c0972dda6081dc696d4f Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 18:37:47 +0200 Subject: [PATCH 010/144] Add redirect for heresphere --- graphql/documents/data/config.graphql | 1 + graphql/schema/types/config.graphql | 4 ++ internal/api/resolver_mutation_configure.go | 1 + internal/api/resolver_query_configuration.go | 3 ++ internal/api/routes_heresphere.go | 50 +++++++++++++++++-- internal/api/server.go | 1 + internal/manager/config/config.go | 9 ++++ .../SettingsInterfacePanel.tsx | 7 +++ ui/v2.5/src/locales/en-GB.json | 6 ++- 9 files changed, 77 insertions(+), 5 deletions(-) diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index 2a56e951252..b7228ec90f3 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -93,6 +93,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult { } handyKey funscriptOffset + redirectHeresphere } fragment ConfigDLNAData on ConfigDLNAResult { diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 6c99393858e..9cefc2c90f6 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -358,6 +358,8 @@ input ConfigInterfaceInput { noBrowser: Boolean """True if we should send notifications to the desktop""" notificationsEnabled: Boolean + """True if we should automatically redirect Heresphere clients to the Heresphere endpoint""" + redirectHeresphere: Boolean } type ConfigDisableDropdownCreate { @@ -387,6 +389,8 @@ type ConfigInterfaceResult { noBrowser: Boolean """True if we should send desktop notifications""" notificationsEnabled: Boolean + """True if we should automatically redirect Heresphere clients to the Heresphere endpoint""" + redirectHeresphere: Boolean """If true, video will autostart on load in the scene player""" autostartVideo: Boolean """If true, video will autostart when loading from play random or play selected""" diff --git a/internal/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index bdc93137f17..5028d7cf326 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -404,6 +404,7 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI setBool(config.NoBrowser, input.NoBrowser) setBool(config.NotificationsEnabled, input.NotificationsEnabled) + setBool(config.RedirectHeresphere, input.RedirectHeresphere) setBool(config.ShowScrubber, input.ShowScrubber) diff --git a/internal/api/resolver_query_configuration.go b/internal/api/resolver_query_configuration.go index 4c9f00aea0d..8e442b1ec79 100644 --- a/internal/api/resolver_query_configuration.go +++ b/internal/api/resolver_query_configuration.go @@ -159,6 +159,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult { language := config.GetLanguage() handyKey := config.GetHandyKey() scriptOffset := config.GetFunscriptOffset() + redirectHeresphere := config.GetRedirectHeresphere() imageLightboxOptions := config.GetImageLightboxOptions() // FIXME - misnamed output field means we have redundant fields disableDropdownCreate := config.GetDisableDropdownCreate() @@ -192,6 +193,8 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult { HandyKey: &handyKey, FunscriptOffset: &scriptOffset, + + RedirectHeresphere: &redirectHeresphere, } } diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index f1ac853c49d..92ac84bbe45 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -189,7 +189,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Head("/", rs.HeresphereIndex) r.Post("/auth", rs.HeresphereLoginToken) - // r.Post("/scan", rs.HeresphereScan) + r.Post("/scan", rs.HeresphereScan) r.Post("/event", rs.HeresphereVideoEvent) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -220,6 +220,21 @@ func relUrlToAbs(r *http.Request, rel string) string { return fmt.Sprintf("%s://%s%s", scheme, host, rel) } +func heresphereHandler() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c := config.GetInstance() + + if strings.Contains(r.UserAgent(), "HereSphere") && c.GetRedirectHeresphere() && !strings.HasPrefix(r.URL.Path, "/heresphere") { + http.Redirect(w, r, "/heresphere", http.StatusSeeOther) + return + } + + next.ServeHTTP(w, r) + }) + } +} + func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { // TODO: This @@ -317,6 +332,7 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoScript { processedScripts := []HeresphereVideoScript{} + // TODO: Use urlbuilders exists, err := rs.resolver.Scene().Interactive(ctx, scene) if err == nil && exists { processedScript := HeresphereVideoScript{ @@ -416,7 +432,7 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques } // TODO: Consider removing, manual scan is faster -/*func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { +func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { var scenes []*models.Scene if err := rs.repository.WithTxn(r.Context(), func(ctx context.Context) error { var err error @@ -456,6 +472,7 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques if err == nil && len(file_ids) > 0 { processedScene.Duration = handleFloat64Value(file_ids[0].Duration * 1000.0) } + //fmt.Printf("Done scene: %v\n", idx) processedScenes[idx] = processedScene } @@ -467,8 +484,9 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques http.Error(w, err.Error(), http.StatusInternalServerError) return } -}*/ +} +// Check against filename for vr modes func FindProjection(path string) (proj HeresphereProjection, stereo HeresphereStereo, eyeswap bool, fov float32, lens HeresphereLens, ipd float32) { proj, stereo, eyeswap, fov, lens, ipd = HeresphereProjectionPerspective, HeresphereStereoMono, false, 180, HeresphereLensLinear, 6.5 @@ -524,6 +542,29 @@ func FindProjection(path string) (proj HeresphereProjection, stereo HeresphereSt return } +// Check against stashdb tags for vr modes +func FindProjectionTags(scene *HeresphereVideoEntry) { + for _, tag := range scene.Tags { + if strings.Contains(tag.Name, "°") { + deg := strings.Replace(tag.Name, "°", "", 0) + if s, err := strconv.ParseFloat(deg, 32); err == nil { + scene.Fov = float32(s) + } + } + if strings.Contains(tag.Name, "Virtual Reality") || strings.Contains(tag.Name, "JAVR") { + if scene.Projection == HeresphereProjectionPerspective { + scene.Projection = HeresphereProjectionEquirectangular + } + if scene.Stereo == HeresphereStereoMono { + scene.Stereo = HeresphereStereoSbs + } + } + if strings.Contains(tag.Name, "Fisheye") { + scene.Projection = HeresphereProjectionFisheye + } + } +} + func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { // This endpoint can receive 2 types of requests // One is a video request (HeresphereAuthReq) @@ -567,6 +608,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re WriteTags: false, WriteHSP: false, } + FindProjectionTags(&processedScene) if user.NeedsMediaSource { processedScene.Media = rs.getVideoMedia(r.Context(), r, scene) @@ -700,7 +742,7 @@ func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} user := HeresphereAuthReq{NeedsMediaSource: true} - if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + if err := json.NewDecoder(r.Body).Decode(&user); err != nil && config.GetInstance().HasCredentials() { http.Error(w, err.Error(), http.StatusBadRequest) return } diff --git a/internal/api/server.go b/internal/api/server.go index be5bad8f027..1a3f1ebd174 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -60,6 +60,7 @@ func Start() error { r.Use(middleware.Heartbeat("/healthz")) r.Use(cors.AllowAll().Handler) + r.Use(heresphereHandler()) r.Use(authenticateHandler()) visitedPluginHandler := manager.GetInstance().SessionStore.VisitedPluginHandler() r.Use(visitedPluginHandler) diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index 44c64392515..9a760311fc8 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -99,6 +99,9 @@ const ( CreateImageClipsFromVideos = "create_image_clip_from_videos" createImageClipsFromVideosDefault = false + RedirectHeresphere = "redirect_heresphere" + redirectHeresphereDefault = false + Host = "host" hostDefault = "0.0.0.0" @@ -872,6 +875,10 @@ func (i *Instance) IsCreateImageClipsFromVideos() bool { return i.getBool(CreateImageClipsFromVideos) } +func (i *Instance) GetRedirectHeresphere() bool { + return i.getBool(RedirectHeresphere) +} + func (i *Instance) GetAPIKey() string { return i.getString(ApiKey) } @@ -1522,6 +1529,8 @@ func (i *Instance) setDefaultValues(write bool) error { i.main.SetDefault(WriteImageThumbnails, writeImageThumbnailsDefault) i.main.SetDefault(CreateImageClipsFromVideos, createImageClipsFromVideosDefault) + i.main.SetDefault(RedirectHeresphere, redirectHeresphereDefault) + i.main.SetDefault(Database, defaultDatabaseFilePath) i.main.SetDefault(dangerousAllowPublicWithoutAuth, dangerousAllowPublicWithoutAuthDefault) diff --git a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx index c44f3ab78b3..9383a2c4416 100644 --- a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx @@ -206,6 +206,13 @@ export const SettingsInterfacePanel: React.FC = () => { checked={ui.abbreviateCounters ?? undefined} onChange={(v) => saveUI({ abbreviateCounters: v })} /> + saveInterface({ heresphereRedirect: v })} + /> diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 0eafd4bca21..a6fbe37694d 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -609,7 +609,11 @@ "language": { "heading": "Language" }, - "max_loop_duration": { + "redirect_heresphere": { + "description": "Redirect Heresphere from the interface to its own API", + "heading": "Redirect Heresphere" + }, + "max_loop_duration": { "description": "Maximum scene duration where scene player will loop the video - 0 to disable", "heading": "Maximum loop duration" }, From a24ab8fe51354b2debf818a73992875c25e95978 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:48:19 +0200 Subject: [PATCH 011/144] Event server implemented (for timestamp use only) --- internal/api/routes_heresphere.go | 67 ++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 92ac84bbe45..c97497114d1 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -88,11 +88,11 @@ type HeresphereVideoSubtitle struct { } type HeresphereVideoTag struct { // Should start with any of the following: "Scene:", "Category:", "Talent:", "Studio:", "Position:" - Name string `json:"name"` - /*Start float64 `json:"start,omitempty"` + Name string `json:"name"` + Start float64 `json:"start,omitempty"` End float64 `json:"end,omitempty"` Track int `json:"track,omitempty"` - Rating float32 `json:"rating,omitempty"`*/ + Rating float32 `json:"rating,omitempty"` } type HeresphereVideoMediaSource struct { Resolution int `json:"resolution"` @@ -165,9 +165,9 @@ type HeresphereVideoEvent struct { Id string `json:"id"` Title string `json:"title"` Event int `json:"event"` - Time float64 `json:"time,omitempty"` - Speed float32 `json:"speed,omitempty"` - Utc float64 `json:"utc,omitempty"` + Time float64 `json:"time"` + Speed float32 `json:"speed"` + Utc float64 `json:"utc"` ConnectionKey string `json:"connectionKey"` } @@ -190,7 +190,6 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Post("/auth", rs.HeresphereLoginToken) r.Post("/scan", rs.HeresphereScan) - r.Post("/event", rs.HeresphereVideoEvent) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -198,6 +197,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Get("/", rs.HeresphereVideoData) r.Get("/hsp", rs.HeresphereVideoHsp) + r.Post("/event", rs.HeresphereVideoEvent) }) }) @@ -236,13 +236,25 @@ func heresphereHandler() func(http.Handler) http.Handler { } func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { - // TODO: This + event := HeresphereVideoEvent{} + if err := json.NewDecoder(r.Body).Decode(&event); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + scene := r.Context().Value(heresphereKey).(*models.Scene) - // Only accessible with auth-key - // Seems we need to trust the Username, or get a user specific auth key - // To have any sort of safety on this endpoint + newTime := event.Time / 1000 + newDuration := scene.PlayDuration + if newTime > scene.ResumeTime { + newDuration += (newTime - scene.ResumeTime) + } - w.WriteHeader(http.StatusNotImplemented) + if _, err := rs.resolver.Mutation().SceneSaveActivity(r.Context(), strconv.Itoa(scene.ID), &newTime, &newDuration); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) } func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Request) { @@ -252,6 +264,20 @@ func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Req func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) { // TODO: This + + /* + + if err := r.withTxn(ctx, func(ctx context.Context) error { + qb := r.repository.Scene + + ret, err = qb.SaveActivity(ctx, sceneID, resumeTime, playDuration) + return err + }); err != nil { + return false, err + } + + */ + w.WriteHeader(http.StatusNotImplemented) } @@ -268,7 +294,9 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc if err == nil { for _, mark := range mark_ids { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Marker:%v", mark.Title), + Name: fmt.Sprintf("Marker:%v", mark.Title), + Start: mark.Seconds * 1000, + End: (mark.Seconds + 60) * 1000, } processedTags = append(processedTags, genTag) } @@ -421,6 +449,7 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques Banner: banner, Library: []HeresphereIndexEntry{library}, } + // TODO: A very helpful feature would be to create multiple libraries from the saved filters: Every saved filter scene should become a separate library, the "All"-Library should remain as well. w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -597,8 +626,8 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Fov: fov, Lens: lens, CameraIPD: ipd, - /*Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp", scene.ID)), - EventServer: relUrlToAbs(r, "/heresphere/event"),*/ + // Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp", scene.ID)), + EventServer: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/event", scene.ID)), Scripts: rs.getVideoScripts(r.Context(), r, scene), Subtitles: rs.getVideoSubtitles(r.Context(), r, scene), Tags: rs.getVideoTags(r.Context(), r, scene), @@ -742,9 +771,11 @@ func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} user := HeresphereAuthReq{NeedsMediaSource: true} - if err := json.NewDecoder(r.Body).Decode(&user); err != nil && config.GetInstance().HasCredentials() { - http.Error(w, err.Error(), http.StatusBadRequest) - return + if !strings.HasSuffix(r.URL.Path, "/event") { + if err := json.NewDecoder(r.Body).Decode(&user); err != nil && config.GetInstance().HasCredentials() { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } } if config.GetInstance().HasCredentials() && From 14a1743b8b149a5eda25bc8a4495f959a70f0126 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:09:04 +0200 Subject: [PATCH 012/144] Auth for events --- internal/api/routes_heresphere.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index c97497114d1..9b24c6562be 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -15,6 +15,7 @@ import ( "github.com/stashapp/stash/pkg/file" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/session" "github.com/stashapp/stash/pkg/txn" ) @@ -626,8 +627,8 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Fov: fov, Lens: lens, CameraIPD: ipd, - // Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp", scene.ID)), - EventServer: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/event", scene.ID)), + // Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), + EventServer: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/event?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), Scripts: rs.getVideoScripts(r.Context(), r, scene), Subtitles: rs.getVideoSubtitles(r.Context(), r, scene), Tags: rs.getVideoTags(r.Context(), r, scene), @@ -740,7 +741,13 @@ func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { } func HeresphereHasValidToken(r *http.Request) bool { - return len(r.Header.Get("auth-token")) > 0 && r.Header.Get("auth-token") == config.GetInstance().GetAPIKey() + apiKey := r.Header.Get("auth-token") + + if apiKey == "" { + apiKey = r.URL.Query().Get(session.ApiKeyParameter) + } + + return len(apiKey) > 0 && apiKey == config.GetInstance().GetAPIKey() } func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { @@ -750,6 +757,7 @@ func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { } library := HeresphereIndexEntry{ Name: msg, + List: []string{relUrlToAbs(r, "/")}, } idx := HeresphereIndex{ Access: HeresphereBadLogin, @@ -771,9 +779,9 @@ func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} user := HeresphereAuthReq{NeedsMediaSource: true} - if !strings.HasSuffix(r.URL.Path, "/event") { + if !strings.HasSuffix(r.URL.Path, "/event") && !strings.HasSuffix(r.URL.Path, "/hsp") { if err := json.NewDecoder(r.Body).Decode(&user); err != nil && config.GetInstance().HasCredentials() { - http.Error(w, err.Error(), http.StatusBadRequest) + writeNotAuthorized(w, r, "Not logged in!") return } } From 8355dcdea1db3c010190ddfec598c40ee9951f2c Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:50:50 +0200 Subject: [PATCH 013/144] Make auth nicer --- internal/api/routes_heresphere.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 9b24c6562be..0e5cdb2b5a1 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -1,10 +1,12 @@ package api import ( + "bytes" "context" "encoding/json" "errors" "fmt" + "io" "net/http" "strconv" "strings" @@ -757,7 +759,7 @@ func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { } library := HeresphereIndexEntry{ Name: msg, - List: []string{relUrlToAbs(r, "/")}, + List: []string{relUrlToAbs(r, "/heresphere/doesnt-exist")}, } idx := HeresphereIndex{ Access: HeresphereBadLogin, @@ -778,21 +780,26 @@ func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "can't read body", http.StatusBadRequest) + return + } + + isAuth := config.GetInstance().HasCredentials() && !HeresphereHasValidToken(r) user := HeresphereAuthReq{NeedsMediaSource: true} - if !strings.HasSuffix(r.URL.Path, "/event") && !strings.HasSuffix(r.URL.Path, "/hsp") { - if err := json.NewDecoder(r.Body).Decode(&user); err != nil && config.GetInstance().HasCredentials() { - writeNotAuthorized(w, r, "Not logged in!") - return - } + if err := json.Unmarshal(body, &user); err != nil && isAuth { + writeNotAuthorized(w, r, "Not logged in!") + return } - if config.GetInstance().HasCredentials() && - !HeresphereHasValidToken(r) && - !strings.HasPrefix(r.URL.Path, "/heresphere/auth") { + if isAuth && !strings.HasPrefix(r.URL.Path, "/heresphere/auth") { writeNotAuthorized(w, r, "Unauthorized!") return } + r.Body = io.NopCloser(bytes.NewBuffer(body)) + ctx := context.WithValue(r.Context(), heresphereUserKey, user) next.ServeHTTP(w, r.WithContext(ctx)) }) From c86dac713d36b25b7f7e02eee88d71bb738ce002 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 22:19:14 +0200 Subject: [PATCH 014/144] Defeat the linter --- internal/api/routes_heresphere.go | 4 ++-- .../SettingsInterfacePanel/SettingsInterfacePanel.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 0e5cdb2b5a1..cc8abcfe66b 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -504,7 +504,7 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request if err == nil && len(file_ids) > 0 { processedScene.Duration = handleFloat64Value(file_ids[0].Duration * 1000.0) } - //fmt.Printf("Done scene: %v\n", idx) + // fmt.Printf("Done scene: %v\n", idx) processedScenes[idx] = processedScene } @@ -578,7 +578,7 @@ func FindProjection(path string) (proj HeresphereProjection, stereo HeresphereSt func FindProjectionTags(scene *HeresphereVideoEntry) { for _, tag := range scene.Tags { if strings.Contains(tag.Name, "°") { - deg := strings.Replace(tag.Name, "°", "", 0) + deg := strings.Replace(tag.Name, "°", "", -1) if s, err := strconv.ParseFloat(deg, 32); err == nil { scene.Fov = float32(s) } diff --git a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx index 9383a2c4416..a07af8baeba 100644 --- a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx @@ -210,8 +210,8 @@ export const SettingsInterfacePanel: React.FC = () => { id="redirect-heresphere" headingID="config.ui.redirect_heresphere.heading" subHeadingID="config.ui.redirect_heresphere.description" - checked={iface.heresphereRedirect ?? undefined} - onChange={(v) => saveInterface({ heresphereRedirect: v })} + checked={iface.redirectHeresphere ?? undefined} + onChange={(v) => saveInterface({ redirectHeresphere: v })} /> From e5d41d15997723d9c3e59af9668e77d436f43b40 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 22:23:55 +0200 Subject: [PATCH 015/144] Make the linter even happier --- internal/api/routes_heresphere.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index cc8abcfe66b..40dcac6079a 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -578,7 +578,7 @@ func FindProjection(path string) (proj HeresphereProjection, stereo HeresphereSt func FindProjectionTags(scene *HeresphereVideoEntry) { for _, tag := range scene.Tags { if strings.Contains(tag.Name, "°") { - deg := strings.Replace(tag.Name, "°", "", -1) + deg := strings.ReplaceAll(tag.Name, "°", "") if s, err := strconv.ParseFloat(deg, 32); err == nil { scene.Fov = float32(s) } From 8de4cc61f644017ceb5c716bc7d89020af0c4db8 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 2 Jun 2023 23:03:41 +0200 Subject: [PATCH 016/144] Small update --- internal/api/routes_heresphere.go | 50 +++++++++++++++++-------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 40dcac6079a..fe9d35d7d42 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -120,7 +120,7 @@ type HeresphereVideoEntry struct { DateAdded string `json:"dateAdded"` Duration float64 `json:"duration,omitempty"` Rating float32 `json:"rating,omitempty"` - Favorites float32 `json:"favorites,omitempty"` + Favorites int `json:"favorites,omitempty"` Comments int `json:"comments"` IsFavorite bool `json:"isFavorite"` Projection HeresphereProjection `json:"projection"` @@ -266,22 +266,25 @@ func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Req } func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) { - // TODO: This - - /* + scene := r.Context().Value(heresphereKey).(*models.Scene) + user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) - if err := r.withTxn(ctx, func(ctx context.Context) error { - qb := r.repository.Scene + if err := txn.WithTxn(r.Context(), rs.repository.TxnManager, func(ctx context.Context) error { + qb := rs.repository.Scene - ret, err = qb.SaveActivity(ctx, sceneID, resumeTime, playDuration) - return err - }); err != nil { - return false, err - } + rating := int((user.Rating / 5.0) * 100.0) + scene.Rating = &rating + // TODO: user.Hsp + // TODO: user.DeleteFile - */ + err := qb.Update(ctx, scene) + return err + }); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } - w.WriteHeader(http.StatusNotImplemented) + w.WriteHeader(http.StatusOK) } func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoTag { @@ -454,9 +457,16 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques } // TODO: A very helpful feature would be to create multiple libraries from the saved filters: Every saved filter scene should become a separate library, the "All"-Library should remain as well. + filters, err := rs.repository.SavedFilter.All(r.Context()) + if err == nil { + for _, filter := range filters { + fmt.Printf("Filter: %v -> %v\n", filter.Name, filter.Filter) + } + } + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - err := json.NewEncoder(w).Encode(idx) + err = json.NewEncoder(w).Encode(idx) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -487,8 +497,8 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request DateAdded: scene.CreatedAt.Format("2006-01-02"), Duration: 60000.0, Rating: 0.0, - Favorites: 0, - Comments: scene.OCounter, + Favorites: scene.OCounter, + Comments: 0, IsFavorite: false, Tags: rs.getVideoTags(r.Context(), r, scene), } @@ -598,10 +608,6 @@ func FindProjectionTags(scene *HeresphereVideoEntry) { } func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { - // This endpoint can receive 2 types of requests - // One is a video request (HeresphereAuthReq) - // Other is an update (HeresphereVideoEntryUpdate) - user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) if user.Tags != nil { rs.HeresphereVideoDataUpdate(w, r) @@ -620,8 +626,8 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re DateAdded: scene.CreatedAt.Format("2006-01-02"), Duration: 60000.0, Rating: 0.0, - Favorites: 0, - Comments: scene.OCounter, + Favorites: scene.OCounter, + Comments: 0, IsFavorite: false, Projection: proj, Stereo: stereo, From 3a1ec268a58a08e4cb698211e62b8b34b6dd0fe8 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Sat, 3 Jun 2023 11:33:35 +0200 Subject: [PATCH 017/144] Fixed redirect --- internal/api/routes_heresphere.go | 72 ++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index fe9d35d7d42..ad1710d089b 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -120,7 +120,7 @@ type HeresphereVideoEntry struct { DateAdded string `json:"dateAdded"` Duration float64 `json:"duration,omitempty"` Rating float32 `json:"rating,omitempty"` - Favorites int `json:"favorites,omitempty"` + Favorites int `json:"favorites"` Comments int `json:"comments"` IsFavorite bool `json:"isFavorite"` Projection HeresphereProjection `json:"projection"` @@ -228,7 +228,7 @@ func heresphereHandler() func(http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c := config.GetInstance() - if strings.Contains(r.UserAgent(), "HereSphere") && c.GetRedirectHeresphere() && !strings.HasPrefix(r.URL.Path, "/heresphere") { + if strings.Contains(r.UserAgent(), "HereSphere") && c.GetRedirectHeresphere() && (r.URL.Path == "/" || strings.HasPrefix(r.URL.Path, "/login")) { http.Redirect(w, r, "/heresphere", http.StatusSeeOther) return } @@ -360,6 +360,24 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc processedTags = append(processedTags, genTag) } + exists, err := rs.resolver.Scene().Interactive(ctx, scene) + if err == nil && exists { + shouldAdd := true + for _, tag := range processedTags { + if strings.Contains(tag.Name, "Tag:Interactive") { + shouldAdd = false + break + } + } + + if shouldAdd { + genTag := HeresphereVideoTag{ + Name: "Tag:Interactive", + } + processedTags = append(processedTags, genTag) + } + } + return processedTags } @@ -376,6 +394,7 @@ func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, } processedScripts = append(processedScripts, processedScript) } + return processedScripts } func (rs heresphereRoutes) getVideoSubtitles(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { @@ -491,16 +510,16 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request for idx, scene := range scenes { // Perform the necessary processing on each scene processedScene := HeresphereVideoEntryShort{ - Link: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v", scene.ID)), - Title: scene.GetTitle(), - DateReleased: scene.CreatedAt.Format("2006-01-02"), - DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60000.0, - Rating: 0.0, - Favorites: scene.OCounter, - Comments: 0, - IsFavorite: false, - Tags: rs.getVideoTags(r.Context(), r, scene), + Link: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v", scene.ID)), + Title: scene.GetTitle(), + // DateReleased: scene.CreatedAt.Format("2006-01-02"), + DateAdded: scene.CreatedAt.Format("2006-01-02"), + Duration: 60000.0, + Rating: 0.0, + Favorites: scene.OCounter, + Comments: 0, + IsFavorite: false, + Tags: rs.getVideoTags(r.Context(), r, scene), } if scene.Date != nil { processedScene.DateReleased = scene.Date.Format("2006-01-02") @@ -603,6 +622,9 @@ func FindProjectionTags(scene *HeresphereVideoEntry) { } if strings.Contains(tag.Name, "Fisheye") { scene.Projection = HeresphereProjectionFisheye + if scene.Stereo == HeresphereStereoMono { + scene.Stereo = HeresphereStereoSbs + } } } } @@ -622,19 +644,19 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Description: scene.Details, ThumbnailImage: relUrlToAbs(r, fmt.Sprintf("/scene/%v/screenshot?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), ThumbnailVideo: relUrlToAbs(r, fmt.Sprintf("/scene/%v/preview?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), - DateReleased: scene.CreatedAt.Format("2006-01-02"), - DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60000.0, - Rating: 0.0, - Favorites: scene.OCounter, - Comments: 0, - IsFavorite: false, - Projection: proj, - Stereo: stereo, - IsEyeSwapped: isEyeSwapped, - Fov: fov, - Lens: lens, - CameraIPD: ipd, + // DateReleased: scene.CreatedAt.Format("2006-01-02"), + DateAdded: scene.CreatedAt.Format("2006-01-02"), + Duration: 60000.0, + Rating: 0.0, + Favorites: scene.OCounter, + Comments: 0, + IsFavorite: false, + Projection: proj, + Stereo: stereo, + IsEyeSwapped: isEyeSwapped, + Fov: fov, + Lens: lens, + CameraIPD: ipd, // Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), EventServer: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/event?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), Scripts: rs.getVideoScripts(r.Context(), r, scene), From 5f4630376f36e4cd618cce7118bde7ee6cac7e4d Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Sat, 3 Jun 2023 11:41:59 +0200 Subject: [PATCH 018/144] Joined FindProjection functions --- internal/api/routes_heresphere.go | 140 +++++++++++++++--------------- 1 file changed, 68 insertions(+), 72 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index ad1710d089b..fcd7108ee6c 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -547,83 +547,80 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request } } -// Check against filename for vr modes -func FindProjection(path string) (proj HeresphereProjection, stereo HeresphereStereo, eyeswap bool, fov float32, lens HeresphereLens, ipd float32) { - proj, stereo, eyeswap, fov, lens, ipd = HeresphereProjectionPerspective, HeresphereStereoMono, false, 180, HeresphereLensLinear, 6.5 - - path = strings.ToUpper(path) - // TODO: Detect video tags - - if strings.Contains(path, "_LR") || strings.Contains(path, "_3DH") { - stereo = HeresphereStereoSbs - } - if strings.Contains(path, "_RL") { - stereo = HeresphereStereoSbs - eyeswap = true - } - if strings.Contains(path, "_TB") || strings.Contains(path, "_3DV") { - stereo = HeresphereStereoTB - } - if strings.Contains(path, "_BT") { - stereo = HeresphereStereoTB - eyeswap = true - } +// Find VR modes from scene +func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntry) { + // Detect VR modes from filename + file := scene.Files.Primary() + if file != nil { + path := strings.ToUpper(file.Basename) + + if strings.Contains(path, "_LR") || strings.Contains(path, "_3DH") { + processedScene.Stereo = HeresphereStereoSbs + } + if strings.Contains(path, "_RL") { + processedScene.Stereo = HeresphereStereoSbs + processedScene.IsEyeSwapped = true + } + if strings.Contains(path, "_TB") || strings.Contains(path, "_3DV") { + processedScene.Stereo = HeresphereStereoTB + } + if strings.Contains(path, "_BT") { + processedScene.Stereo = HeresphereStereoTB + processedScene.IsEyeSwapped = true + } - if strings.Contains(path, "_EAC360") || strings.Contains(path, "_360EAC") { - proj = HeresphereProjectionEquirectangularCubemap - } - if strings.Contains(path, "_360") { - proj = HeresphereProjectionEquirectangular360 - } - if strings.Contains(path, "_F180") || strings.Contains(path, "_180F") || strings.Contains(path, "_VR180") { - proj = HeresphereProjectionFisheye - } else if strings.Contains(path, "_180") { - proj = HeresphereProjectionEquirectangular - } - if strings.Contains(path, "_MKX200") { - proj = HeresphereProjectionFisheye - fov = 200 - lens = HeresphereLensMKX200 - } - if strings.Contains(path, "_MKX220") { - proj = HeresphereProjectionFisheye - fov = 220 - lens = HeresphereLensMKX220 - } - if strings.Contains(path, "_RF52") { - proj = HeresphereProjectionFisheye - fov = 190 - } - if strings.Contains(path, "_VRCA220") { - proj = HeresphereProjectionFisheye - fov = 220 - lens = HeresphereLensVRCA220 + if strings.Contains(path, "_EAC360") || strings.Contains(path, "_360EAC") { + processedScene.Projection = HeresphereProjectionEquirectangularCubemap + } + if strings.Contains(path, "_360") { + processedScene.Projection = HeresphereProjectionEquirectangular360 + } + if strings.Contains(path, "_F180") || strings.Contains(path, "_180F") || strings.Contains(path, "_VR180") { + processedScene.Projection = HeresphereProjectionFisheye + } else if strings.Contains(path, "_180") { + processedScene.Projection = HeresphereProjectionEquirectangular + } + if strings.Contains(path, "_MKX200") { + processedScene.Projection = HeresphereProjectionFisheye + processedScene.Fov = 200.0 + processedScene.Lens = HeresphereLensMKX200 + } + if strings.Contains(path, "_MKX220") { + processedScene.Projection = HeresphereProjectionFisheye + processedScene.Fov = 220.0 + processedScene.Lens = HeresphereLensMKX220 + } + if strings.Contains(path, "_RF52") { + processedScene.Projection = HeresphereProjectionFisheye + processedScene.Fov = 190.0 + } + if strings.Contains(path, "_VRCA220") { + processedScene.Projection = HeresphereProjectionFisheye + processedScene.Fov = 220.0 + processedScene.Lens = HeresphereLensVRCA220 + } } - return -} - -// Check against stashdb tags for vr modes -func FindProjectionTags(scene *HeresphereVideoEntry) { - for _, tag := range scene.Tags { + // Detect VR modes from tags + for _, tag := range processedScene.Tags { if strings.Contains(tag.Name, "°") { deg := strings.ReplaceAll(tag.Name, "°", "") if s, err := strconv.ParseFloat(deg, 32); err == nil { - scene.Fov = float32(s) + processedScene.Fov = float32(s) } } if strings.Contains(tag.Name, "Virtual Reality") || strings.Contains(tag.Name, "JAVR") { - if scene.Projection == HeresphereProjectionPerspective { - scene.Projection = HeresphereProjectionEquirectangular + if processedScene.Projection == HeresphereProjectionPerspective { + processedScene.Projection = HeresphereProjectionEquirectangular } - if scene.Stereo == HeresphereStereoMono { - scene.Stereo = HeresphereStereoSbs + if processedScene.Stereo == HeresphereStereoMono { + processedScene.Stereo = HeresphereStereoSbs } } if strings.Contains(tag.Name, "Fisheye") { - scene.Projection = HeresphereProjectionFisheye - if scene.Stereo == HeresphereStereoMono { - scene.Stereo = HeresphereStereoSbs + processedScene.Projection = HeresphereProjectionFisheye + if processedScene.Stereo == HeresphereStereoMono { + processedScene.Stereo = HeresphereStereoSbs } } } @@ -637,7 +634,6 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } scene := r.Context().Value(heresphereKey).(*models.Scene) - proj, stereo, isEyeSwapped, fov, lens, ipd := FindProjection(scene.Files.Primary().Basename) processedScene := HeresphereVideoEntry{ Access: HeresphereMember, Title: scene.GetTitle(), @@ -651,12 +647,12 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Favorites: scene.OCounter, Comments: 0, IsFavorite: false, - Projection: proj, - Stereo: stereo, - IsEyeSwapped: isEyeSwapped, - Fov: fov, - Lens: lens, - CameraIPD: ipd, + Projection: HeresphereProjectionPerspective, + Stereo: HeresphereStereoMono, + IsEyeSwapped: false, + Fov: 180.0, + Lens: HeresphereLensLinear, + CameraIPD: 6.5, // Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), EventServer: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/event?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), Scripts: rs.getVideoScripts(r.Context(), r, scene), @@ -668,7 +664,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re WriteTags: false, WriteHSP: false, } - FindProjectionTags(&processedScene) + FindProjectionTags(scene, &processedScene) if user.NeedsMediaSource { processedScene.Media = rs.getVideoMedia(r.Context(), r, scene) From 0b2c4c223d5b125db660f40f3256b8b8f8ad4cb0 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Sun, 4 Jun 2023 23:22:58 +0200 Subject: [PATCH 019/144] Use urlbuilder --- internal/api/routes_heresphere.go | 123 ++++++++++++++++++------------ internal/api/server.go | 31 ++++---- 2 files changed, 92 insertions(+), 62 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index fcd7108ee6c..42776f139da 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/go-chi/chi" + "github.com/stashapp/stash/internal/api/urlbuilders" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/pkg/file" @@ -81,8 +82,6 @@ type HeresphereVideoScript struct { Name string `json:"name"` Url string `json:"url"` Rating float32 `json:"rating,omitempty"` - // TODO: Floats become null, same with lists, should have default value instead - // This is technically an api violation } type HeresphereVideoSubtitle struct { Name string `json:"name"` @@ -90,7 +89,6 @@ type HeresphereVideoSubtitle struct { Url string `json:"url"` } type HeresphereVideoTag struct { - // Should start with any of the following: "Scene:", "Category:", "Talent:", "Studio:", "Position:" Name string `json:"name"` Start float64 `json:"start,omitempty"` End float64 `json:"end,omitempty"` @@ -192,7 +190,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Head("/", rs.HeresphereIndex) r.Post("/auth", rs.HeresphereLoginToken) - r.Post("/scan", rs.HeresphereScan) + //r.Post("/scan", rs.HeresphereScan) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -207,22 +205,6 @@ func (rs heresphereRoutes) Routes() chi.Router { return r } -func relUrlToAbs(r *http.Request, rel string) string { - // Get the scheme (http or https) from the request - scheme := "http" - if r.TLS != nil { - scheme = "https" - } - - // Get the host from the request - host := r.Host - - // TODO: Support Forwarded standard - - // Combine the scheme, host, and relative path to form the absolute URL - return fmt.Sprintf("%s://%s%s", scheme, host, rel) -} - func heresphereHandler() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -268,6 +250,7 @@ func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Req func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(heresphereKey).(*models.Scene) user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) + fileDeleter := file.NewDeleter() if err := txn.WithTxn(r.Context(), rs.repository.TxnManager, func(ctx context.Context) error { qb := rs.repository.Scene @@ -275,15 +258,28 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h rating := int((user.Rating / 5.0) * 100.0) scene.Rating = &rating // TODO: user.Hsp - // TODO: user.DeleteFile + + /*if user.DeleteFile { + qe := rs.repository.File + if err := scene.LoadPrimaryFile(r.Context(), qe); err != nil { + ff := scene.Files.Primary() + if ff != nil { + if err := file.Destroy(ctx, qe, ff, fileDeleter, true); err != nil { + return fmt.Errorf("destroying file %s: %w", ff.Base().Path, err) + } + } + } + }*/ err := qb.Update(ctx, scene) return err }); err != nil { + fileDeleter.Rollback() w.WriteHeader(http.StatusInternalServerError) return } + fileDeleter.Commit() w.WriteHeader(http.StatusOK) } @@ -388,8 +384,11 @@ func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, exists, err := rs.resolver.Scene().Interactive(ctx, scene) if err == nil && exists { processedScript := HeresphereVideoScript{ - Name: "Default script", - Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/funscript?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), + Name: "Default script", + Url: fmt.Sprintf("%s?apikey=%v", + urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetFunscriptURL(), + config.GetInstance().GetAPIKey(), + ), Rating: 4.2, } processedScripts = append(processedScripts, processedScript) @@ -406,7 +405,12 @@ func (rs heresphereRoutes) getVideoSubtitles(ctx context.Context, r *http.Reques processedCaption := HeresphereVideoSubtitle{ Name: caption.Filename, Language: caption.LanguageCode, - Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/caption?lang=%v&type=%v&apikey=%v", scene.ID, caption.LanguageCode, caption.CaptionType, config.GetInstance().GetAPIKey())), + Url: fmt.Sprintf("%s?lang=%v&type=%v&apikey=%v", + urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetCaptionURL(), + caption.LanguageCode, + caption.CaptionType, + config.GetInstance().GetAPIKey(), + ), } processedSubtitles = append(processedSubtitles, processedCaption) } @@ -427,7 +431,7 @@ func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, s Height: mediaFile.Height, Width: mediaFile.Width, Size: mediaFile.Size, - Url: relUrlToAbs(r, fmt.Sprintf("/scene/%v/stream?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), + Url: urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamURL(config.GetInstance().GetAPIKey()).String(), } mediaTypes[mediaFile.Format] = append(mediaTypes[mediaFile.Format], processedEntry) } @@ -446,8 +450,14 @@ func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, s func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { banner := HeresphereBanner{ - Image: relUrlToAbs(r, "/apple-touch-icon.png"), - Link: relUrlToAbs(r, "/"), + Image: fmt.Sprintf("%s%s", + GetBaseURL(r), + "/apple-touch-icon.png", + ), + Link: fmt.Sprintf("%s%s", + GetBaseURL(r), + "/", + ), } var scenes []*models.Scene @@ -462,7 +472,10 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques sceneUrls := make([]string, len(scenes)) for idx, scene := range scenes { - sceneUrls[idx] = relUrlToAbs(r, fmt.Sprintf("/heresphere/%v", scene.ID)) + sceneUrls[idx] = fmt.Sprintf("%s/heresphere/%v", + GetBaseURL(r), + scene.ID, + ) } library := HeresphereIndexEntry{ @@ -474,18 +487,10 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques Banner: banner, Library: []HeresphereIndexEntry{library}, } - // TODO: A very helpful feature would be to create multiple libraries from the saved filters: Every saved filter scene should become a separate library, the "All"-Library should remain as well. - - filters, err := rs.repository.SavedFilter.All(r.Context()) - if err == nil { - for _, filter := range filters { - fmt.Printf("Filter: %v -> %v\n", filter.Name, filter.Filter) - } - } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - err = json.NewEncoder(w).Encode(idx) + err := json.NewEncoder(w).Encode(idx) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -493,7 +498,7 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques } // TODO: Consider removing, manual scan is faster -func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { +/*func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { var scenes []*models.Scene if err := rs.repository.WithTxn(r.Context(), func(ctx context.Context) error { var err error @@ -545,7 +550,7 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request http.Error(w, err.Error(), http.StatusInternalServerError) return } -} +}*/ // Find VR modes from scene func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntry) { @@ -635,11 +640,17 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re scene := r.Context().Value(heresphereKey).(*models.Scene) processedScene := HeresphereVideoEntry{ - Access: HeresphereMember, - Title: scene.GetTitle(), - Description: scene.Details, - ThumbnailImage: relUrlToAbs(r, fmt.Sprintf("/scene/%v/screenshot?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), - ThumbnailVideo: relUrlToAbs(r, fmt.Sprintf("/scene/%v/preview?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), + Access: HeresphereMember, + Title: scene.GetTitle(), + Description: scene.Details, + ThumbnailImage: fmt.Sprintf("%s?apikey=%v", + urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetScreenshotURL(), + config.GetInstance().GetAPIKey(), + ), + ThumbnailVideo: fmt.Sprintf("%s?apikey=%v", + urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamPreviewURL(), + config.GetInstance().GetAPIKey(), + ), // DateReleased: scene.CreatedAt.Format("2006-01-02"), DateAdded: scene.CreatedAt.Format("2006-01-02"), Duration: 60000.0, @@ -653,8 +664,16 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Fov: 180.0, Lens: HeresphereLensLinear, CameraIPD: 6.5, - // Hsp: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/hsp?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), - EventServer: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v/event?apikey=%v", scene.ID, config.GetInstance().GetAPIKey())), + /*Hsp: fmt.Sprintf("%s/heresphere/%v/hsp?apikey=%v", + GetBaseURL(r), + scene.ID, + config.GetInstance().GetAPIKey(), + ),*/ + EventServer: fmt.Sprintf("%s/heresphere/%v/event?apikey=%v", + GetBaseURL(r), + scene.ID, + config.GetInstance().GetAPIKey(), + ), Scripts: rs.getVideoScripts(r.Context(), r, scene), Subtitles: rs.getVideoSubtitles(r.Context(), r, scene), Tags: rs.getVideoTags(r.Context(), r, scene), @@ -778,12 +797,18 @@ func HeresphereHasValidToken(r *http.Request) bool { func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { banner := HeresphereBanner{ - Image: relUrlToAbs(r, "/apple-touch-icon.png"), - Link: relUrlToAbs(r, "/"), + Image: fmt.Sprintf("%s%s", + GetBaseURL(r), + "/apple-touch-icon.png", + ), + Link: fmt.Sprintf("%s%s", + GetBaseURL(r), + "/", + ), } library := HeresphereIndexEntry{ Name: msg, - List: []string{relUrlToAbs(r, "/heresphere/doesnt-exist")}, + List: []string{fmt.Sprintf("%s/heresphere/doesnt-exist", GetBaseURL(r))}, } idx := HeresphereIndex{ Access: HeresphereBadLogin, diff --git a/internal/api/server.go b/internal/api/server.go index 1a3f1ebd174..63a25768847 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -530,23 +530,28 @@ var ( BaseURLCtxKey = &contextKey{"BaseURL"} ) -func BaseURLMiddleware(next http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() +func GetBaseURL(r *http.Request) string { + scheme := "http" + if strings.Compare("https", r.URL.Scheme) == 0 || r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { + scheme = "https" + } + prefix := getProxyPrefix(r) - scheme := "http" - if strings.Compare("https", r.URL.Scheme) == 0 || r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { - scheme = "https" - } - prefix := getProxyPrefix(r) + baseURL := scheme + "://" + r.Host + prefix + + externalHost := config.GetInstance().GetExternalHost() + if externalHost != "" { + baseURL = externalHost + prefix + } - baseURL := scheme + "://" + r.Host + prefix + return baseURL +} - externalHost := config.GetInstance().GetExternalHost() - if externalHost != "" { - baseURL = externalHost + prefix - } +func BaseURLMiddleware(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + baseURL := GetBaseURL(r) r = r.WithContext(context.WithValue(ctx, BaseURLCtxKey, baseURL)) next.ServeHTTP(w, r) From f5f386f5a05ac82707f06cb36dc1fb1ed7ea429f Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 00:26:28 +0200 Subject: [PATCH 020/144] /scan is now faster (43s -> 8s) --- internal/api/routes_heresphere.go | 379 +++++++++++++++--------------- 1 file changed, 192 insertions(+), 187 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 42776f139da..820661604a8 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/go-chi/chi" + "github.com/stashapp/stash/internal/api/loaders" "github.com/stashapp/stash/internal/api/urlbuilders" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" @@ -60,6 +61,8 @@ const ( HeresphereLensVRCA220 HeresphereLens = "VRCA220" ) +const HeresphereAuthHeader = "auth-token" + type HeresphereAuthResp struct { AuthToken string `json:"auth-token"` Access int `json:"access"` @@ -190,7 +193,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Head("/", rs.HeresphereIndex) r.Post("/auth", rs.HeresphereLoginToken) - //r.Post("/scan", rs.HeresphereScan) + r.Post("/scan", rs.HeresphereScan) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -259,7 +262,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h scene.Rating = &rating // TODO: user.Hsp - /*if user.DeleteFile { + if user.DeleteFile { qe := rs.repository.File if err := scene.LoadPrimaryFile(r.Context(), qe); err != nil { ff := scene.Files.Primary() @@ -269,7 +272,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h } } } - }*/ + } err := qb.Update(ctx, scene) return err @@ -283,16 +286,10 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h w.WriteHeader(http.StatusOK) } -func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoTag { +func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) []HeresphereVideoTag { processedTags := []HeresphereVideoTag{} - if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - return scene.LoadRelationships(ctx, rs.repository.Scene) - }); err != nil { - return processedTags - } - - mark_ids, err := rs.resolver.Scene().SceneMarkers(ctx, scene) + mark_ids, err := rs.repository.SceneMarker.FindBySceneID(r.Context(), scene.ID) if err == nil { for _, mark := range mark_ids { genTag := HeresphereVideoTag{ @@ -304,37 +301,43 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc } } - gallery_ids, err := rs.resolver.Scene().Galleries(ctx, scene) - if err == nil { - for _, gal := range gallery_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Gallery:%v", gal.GetTitle()), + if scene.GalleryIDs.Loaded() { + gallery_ids, errs := loaders.From(r.Context()).GalleryByID.LoadAll(scene.GalleryIDs.List()) + if firstError(errs) == nil { + for _, gal := range gallery_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Gallery:%v", gal.GetTitle()), + } + processedTags = append(processedTags, genTag) } - processedTags = append(processedTags, genTag) } } - tag_ids, err := rs.resolver.Scene().Tags(ctx, scene) - if err == nil { - for _, tag := range tag_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Tag:%v", tag.Name), + if scene.TagIDs.Loaded() { + tag_ids, errs := loaders.From(r.Context()).TagByID.LoadAll(scene.TagIDs.List()) + if firstError(errs) == nil { + for _, tag := range tag_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Tag:%v", tag.Name), + } + processedTags = append(processedTags, genTag) } - processedTags = append(processedTags, genTag) } } - perf_ids, err := rs.resolver.Scene().Performers(ctx, scene) - if err == nil { - for _, perf := range perf_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Talent:%s", perf.Name), + if scene.PerformerIDs.Loaded() { + perf_ids, errs := loaders.From(r.Context()).PerformerByID.LoadAll(scene.PerformerIDs.List()) + if firstError(errs) == nil { + for _, perf := range perf_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Talent:%s", perf.Name), + } + processedTags = append(processedTags, genTag) } - processedTags = append(processedTags, genTag) } } - movie_ids, err := rs.resolver.Scene().Movies(ctx, scene) + /*movie_ids, err := rs.resolver.Scene().Movies(r.Context(), scene) if err == nil { for _, movie := range movie_ids { if movie.Movie != nil { @@ -344,31 +347,15 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc processedTags = append(processedTags, genTag) } } - } - - // stash_ids, err := rs.resolver.Scene().StashIds(ctx, scene) - - studio_id, err := rs.resolver.Scene().Studio(ctx, scene) - if err == nil && studio_id != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Studio:%v", studio_id.Name.String), - } - processedTags = append(processedTags, genTag) - } + }*/ - exists, err := rs.resolver.Scene().Interactive(ctx, scene) - if err == nil && exists { - shouldAdd := true - for _, tag := range processedTags { - if strings.Contains(tag.Name, "Tag:Interactive") { - shouldAdd = false - break - } - } + // stash_ids, err := rs.resolver.Scene().StashIds(r.Context(), scene) - if shouldAdd { + if scene.StudioID != nil { + studio_id, err := loaders.From(r.Context()).StudioByID.Load(*scene.StudioID) + if err == nil && studio_id != nil { genTag := HeresphereVideoTag{ - Name: "Tag:Interactive", + Name: fmt.Sprintf("Studio:%v", studio_id.Name.String), } processedTags = append(processedTags, genTag) } @@ -377,12 +364,10 @@ func (rs heresphereRoutes) getVideoTags(ctx context.Context, r *http.Request, sc return processedTags } -func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoScript { +func (rs heresphereRoutes) getVideoScripts(r *http.Request, scene *models.Scene) []HeresphereVideoScript { processedScripts := []HeresphereVideoScript{} - // TODO: Use urlbuilders - exists, err := rs.resolver.Scene().Interactive(ctx, scene) - if err == nil && exists { + if interactive, err := rs.resolver.Scene().Interactive(r.Context(), scene); err == nil && interactive { processedScript := HeresphereVideoScript{ Name: "Default script", Url: fmt.Sprintf("%s?apikey=%v", @@ -396,11 +381,10 @@ func (rs heresphereRoutes) getVideoScripts(ctx context.Context, r *http.Request, return processedScripts } -func (rs heresphereRoutes) getVideoSubtitles(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { +func (rs heresphereRoutes) getVideoSubtitles(r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { processedSubtitles := []HeresphereVideoSubtitle{} - captions_id, err := rs.resolver.Scene().Captions(ctx, scene) - if err == nil { + if captions_id, err := rs.resolver.Scene().Captions(r.Context(), scene); err == nil { for _, caption := range captions_id { processedCaption := HeresphereVideoSubtitle{ Name: caption.Filename, @@ -418,13 +402,12 @@ func (rs heresphereRoutes) getVideoSubtitles(ctx context.Context, r *http.Reques return processedSubtitles } -func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, scene *models.Scene) []HeresphereVideoMedia { +func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) []HeresphereVideoMedia { processedMedia := []HeresphereVideoMedia{} mediaTypes := make(map[string][]HeresphereVideoMediaSource) - file_ids, err := rs.resolver.Scene().Files(ctx, scene) - if err == nil { + if file_ids, err := rs.resolver.Scene().Files(r.Context(), scene); err == nil { for _, mediaFile := range file_ids { processedEntry := HeresphereVideoMediaSource{ Resolution: mediaFile.Height, @@ -433,7 +416,8 @@ func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, s Size: mediaFile.Size, Url: urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamURL(config.GetInstance().GetAPIKey()).String(), } - mediaTypes[mediaFile.Format] = append(mediaTypes[mediaFile.Format], processedEntry) + typeName := fmt.Sprintf("%s %s", mediaFile.Format, mediaFile.VideoCodec) + mediaTypes[typeName] = append(mediaTypes[typeName], processedEntry) } } @@ -443,7 +427,6 @@ func (rs heresphereRoutes) getVideoMedia(ctx context.Context, r *http.Request, s Sources: sources, }) } - // TODO: Transcode etc. /scene/%v/stream.mp4?resolution=ORIGINAL return processedMedia } @@ -498,48 +481,57 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques } // TODO: Consider removing, manual scan is faster -/*func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { +func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { var scenes []*models.Scene - if err := rs.repository.WithTxn(r.Context(), func(ctx context.Context) error { + processedScenes := []HeresphereVideoEntryShort{} + if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { var err error - scenes, err = rs.repository.Scene.All(ctx) - return err - }); err != nil { - http.Error(w, "Failed to fetch scenes!", http.StatusInternalServerError) - return - } - // TODO: /scan is damn slow - // Processing each scene and creating a new list - processedScenes := make([]HeresphereVideoEntryShort, len(scenes)) - for idx, scene := range scenes { - // Perform the necessary processing on each scene - processedScene := HeresphereVideoEntryShort{ - Link: relUrlToAbs(r, fmt.Sprintf("/heresphere/%v", scene.ID)), - Title: scene.GetTitle(), - // DateReleased: scene.CreatedAt.Format("2006-01-02"), - DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60000.0, - Rating: 0.0, - Favorites: scene.OCounter, - Comments: 0, - IsFavorite: false, - Tags: rs.getVideoTags(r.Context(), r, scene), - } - if scene.Date != nil { - processedScene.DateReleased = scene.Date.Format("2006-01-02") - } - if scene.Rating != nil { - isFavorite := *scene.Rating > 85 - processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 - processedScene.IsFavorite = isFavorite + if scenes, err = rs.repository.Scene.All(ctx); err != nil { + return err } - file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) - if err == nil && len(file_ids) > 0 { - processedScene.Duration = handleFloat64Value(file_ids[0].Duration * 1000.0) + + // Processing each scene and creating a new list + for idx, scene := range scenes { + if err = scene.LoadRelationships(ctx, rs.repository.Scene); err != nil { + continue + } + + // Perform the necessary processing on each scene + processedScene := HeresphereVideoEntryShort{ + Link: fmt.Sprintf("%s/heresphere/%v", + GetBaseURL(r), + scene.ID, + ), + Title: scene.GetTitle(), + DateAdded: scene.CreatedAt.Format("2006-01-02"), + Duration: 60000.0, + Rating: 0.0, + Favorites: scene.OCounter, + Comments: 0, + IsFavorite: false, + Tags: rs.getVideoTags(r, scene), + } + if scene.Date != nil { + processedScene.DateReleased = scene.Date.Format("2006-01-02") + } + if scene.Rating != nil { + isFavorite := *scene.Rating > 85 + processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 + processedScene.IsFavorite = isFavorite + } + file := scene.Files.Primary() + if file != nil { + processedScene.Duration = handleFloat64Value(file.Duration * 1000.0) + } + + processedScenes = append(processedScenes, processedScene) } - // fmt.Printf("Done scene: %v\n", idx) - processedScenes[idx] = processedScene + + return err + }); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return } // Create a JSON encoder for the response writer @@ -550,10 +542,34 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques http.Error(w, err.Error(), http.StatusInternalServerError) return } -}*/ +} // Find VR modes from scene func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntry) { + // Detect VR modes from tags + for _, tag := range processedScene.Tags { + if strings.Contains(tag.Name, "°") { + deg := strings.ReplaceAll(tag.Name, "°", "") + if s, err := strconv.ParseFloat(deg, 32); err == nil { + processedScene.Fov = float32(s) + } + } + if strings.Contains(tag.Name, "Virtual Reality") || strings.Contains(tag.Name, "JAVR") { + if processedScene.Projection == HeresphereProjectionPerspective { + processedScene.Projection = HeresphereProjectionEquirectangular + } + if processedScene.Stereo == HeresphereStereoMono { + processedScene.Stereo = HeresphereStereoSbs + } + } + if strings.Contains(tag.Name, "Fisheye") { + processedScene.Projection = HeresphereProjectionFisheye + if processedScene.Stereo == HeresphereStereoMono { + processedScene.Stereo = HeresphereStereoSbs + } + } + } + // Detect VR modes from filename file := scene.Files.Primary() if file != nil { @@ -605,30 +621,6 @@ func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntr processedScene.Lens = HeresphereLensVRCA220 } } - - // Detect VR modes from tags - for _, tag := range processedScene.Tags { - if strings.Contains(tag.Name, "°") { - deg := strings.ReplaceAll(tag.Name, "°", "") - if s, err := strconv.ParseFloat(deg, 32); err == nil { - processedScene.Fov = float32(s) - } - } - if strings.Contains(tag.Name, "Virtual Reality") || strings.Contains(tag.Name, "JAVR") { - if processedScene.Projection == HeresphereProjectionPerspective { - processedScene.Projection = HeresphereProjectionEquirectangular - } - if processedScene.Stereo == HeresphereStereoMono { - processedScene.Stereo = HeresphereStereoSbs - } - } - if strings.Contains(tag.Name, "Fisheye") { - processedScene.Projection = HeresphereProjectionFisheye - if processedScene.Stereo == HeresphereStereoMono { - processedScene.Stereo = HeresphereStereoSbs - } - } - } } func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { @@ -639,66 +631,79 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } scene := r.Context().Value(heresphereKey).(*models.Scene) - processedScene := HeresphereVideoEntry{ - Access: HeresphereMember, - Title: scene.GetTitle(), - Description: scene.Details, - ThumbnailImage: fmt.Sprintf("%s?apikey=%v", - urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetScreenshotURL(), - config.GetInstance().GetAPIKey(), - ), - ThumbnailVideo: fmt.Sprintf("%s?apikey=%v", - urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamPreviewURL(), - config.GetInstance().GetAPIKey(), - ), - // DateReleased: scene.CreatedAt.Format("2006-01-02"), - DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60000.0, - Rating: 0.0, - Favorites: scene.OCounter, - Comments: 0, - IsFavorite: false, - Projection: HeresphereProjectionPerspective, - Stereo: HeresphereStereoMono, - IsEyeSwapped: false, - Fov: 180.0, - Lens: HeresphereLensLinear, - CameraIPD: 6.5, - /*Hsp: fmt.Sprintf("%s/heresphere/%v/hsp?apikey=%v", - GetBaseURL(r), - scene.ID, - config.GetInstance().GetAPIKey(), - ),*/ - EventServer: fmt.Sprintf("%s/heresphere/%v/event?apikey=%v", - GetBaseURL(r), - scene.ID, - config.GetInstance().GetAPIKey(), - ), - Scripts: rs.getVideoScripts(r.Context(), r, scene), - Subtitles: rs.getVideoSubtitles(r.Context(), r, scene), - Tags: rs.getVideoTags(r.Context(), r, scene), - Media: []HeresphereVideoMedia{}, - WriteFavorite: false, - WriteRating: false, - WriteTags: false, - WriteHSP: false, - } - FindProjectionTags(scene, &processedScene) - if user.NeedsMediaSource { - processedScene.Media = rs.getVideoMedia(r.Context(), r, scene) - } - if scene.Date != nil { - processedScene.DateReleased = scene.Date.Format("2006-01-02") - } - if scene.Rating != nil { - isFavorite := *scene.Rating > 85 - processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 - processedScene.IsFavorite = isFavorite - } - file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) - if err == nil && len(file_ids) > 0 { - processedScene.Duration = handleFloat64Value(file_ids[0].Duration * 1000.0) + var err error + processedScene := HeresphereVideoEntry{} + if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + if err = scene.LoadRelationships(ctx, rs.repository.Scene); err != nil { + return err + } + + processedScene := HeresphereVideoEntry{ + Access: HeresphereMember, + Title: scene.GetTitle(), + Description: scene.Details, + ThumbnailImage: fmt.Sprintf("%s?apikey=%v", + urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetScreenshotURL(), + config.GetInstance().GetAPIKey(), + ), + ThumbnailVideo: fmt.Sprintf("%s?apikey=%v", + urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamPreviewURL(), + config.GetInstance().GetAPIKey(), + ), + DateAdded: scene.CreatedAt.Format("2006-01-02"), + Duration: 60000.0, + Rating: 0.0, + Favorites: scene.OCounter, + Comments: 0, + IsFavorite: false, + Projection: HeresphereProjectionPerspective, + Stereo: HeresphereStereoMono, + IsEyeSwapped: false, + Fov: 180.0, + Lens: HeresphereLensLinear, + CameraIPD: 6.5, + /*Hsp: fmt.Sprintf("%s/heresphere/%v/hsp?apikey=%v", + GetBaseURL(r), + scene.ID, + config.GetInstance().GetAPIKey(), + ),*/ + EventServer: fmt.Sprintf("%s/heresphere/%v/event?apikey=%v", + GetBaseURL(r), + scene.ID, + config.GetInstance().GetAPIKey(), + ), + Scripts: rs.getVideoScripts(r, scene), + Subtitles: rs.getVideoSubtitles(r, scene), + Tags: rs.getVideoTags(r, scene), + Media: []HeresphereVideoMedia{}, + WriteFavorite: false, + WriteRating: false, + WriteTags: false, + WriteHSP: false, + } + FindProjectionTags(scene, &processedScene) + + if user.NeedsMediaSource { + processedScene.Media = rs.getVideoMedia(r, scene) + } + if scene.Date != nil { + processedScene.DateReleased = scene.Date.Format("2006-01-02") + } + if scene.Rating != nil { + isFavorite := *scene.Rating > 85 + processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 + processedScene.IsFavorite = isFavorite + } + file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) + if err == nil && len(file_ids) > 0 { + processedScene.Duration = handleFloat64Value(file_ids[0].Duration * 1000.0) + } + + return err + }); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return } // Create a JSON encoder for the response writer @@ -786,7 +791,7 @@ func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { } func HeresphereHasValidToken(r *http.Request) bool { - apiKey := r.Header.Get("auth-token") + apiKey := r.Header.Get(HeresphereAuthHeader) if apiKey == "" { apiKey = r.URL.Query().Get(session.ApiKeyParameter) @@ -836,7 +841,7 @@ func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { } isAuth := config.GetInstance().HasCredentials() && !HeresphereHasValidToken(r) - user := HeresphereAuthReq{NeedsMediaSource: true} + user := HeresphereAuthReq{NeedsMediaSource: true, DeleteFile: false} if err := json.Unmarshal(body, &user); err != nil && isAuth { writeNotAuthorized(w, r, "Not logged in!") return From 2e8a33e151c1cfc7862dd6241157e8d0b485602d Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 00:29:25 +0200 Subject: [PATCH 021/144] Forgot an assignment --- internal/api/routes_heresphere.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 820661604a8..7a54b69875c 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -492,7 +492,7 @@ func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request } // Processing each scene and creating a new list - for idx, scene := range scenes { + for _, scene := range scenes { if err = scene.LoadRelationships(ctx, rs.repository.Scene); err != nil { continue } @@ -632,14 +632,14 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re scene := r.Context().Value(heresphereKey).(*models.Scene) - var err error processedScene := HeresphereVideoEntry{} if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + var err error if err = scene.LoadRelationships(ctx, rs.repository.Scene); err != nil { return err } - processedScene := HeresphereVideoEntry{ + processedScene = HeresphereVideoEntry{ Access: HeresphereMember, Title: scene.GetTitle(), Description: scene.Details, @@ -709,7 +709,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re // Create a JSON encoder for the response writer w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - err = json.NewEncoder(w).Encode(processedScene) + err := json.NewEncoder(w).Encode(processedScene) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return From 7c171014c9f280b75d50ac25ba6d852b9af0c8ff Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 02:09:19 +0200 Subject: [PATCH 022/144] Transcoding --- internal/api/routes_heresphere.go | 48 +++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 7a54b69875c..44ebbc51b71 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -261,6 +261,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h rating := int((user.Rating / 5.0) * 100.0) scene.Rating = &rating // TODO: user.Hsp + // TODO: user.Tags crosscheck if user.DeleteFile { qe := rs.repository.File @@ -407,18 +408,53 @@ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) [ mediaTypes := make(map[string][]HeresphereVideoMediaSource) - if file_ids, err := rs.resolver.Scene().Files(r.Context(), scene); err == nil { - for _, mediaFile := range file_ids { - processedEntry := HeresphereVideoMediaSource{ + // if file_ids, err := rs.resolver.Scene().Files(r.Context(), scene); err == nil { + if err := scene.LoadPrimaryFile(r.Context(), rs.repository.File); err == nil { + if mediaFile := scene.Files.Primary(); mediaFile != nil { + + // for _, mediaFile := range file_ids { + + sourceUrl := urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamURL("").String() + processedEntry := &HeresphereVideoMediaSource{ Resolution: mediaFile.Height, Height: mediaFile.Height, Width: mediaFile.Width, Size: mediaFile.Size, - Url: urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamURL(config.GetInstance().GetAPIKey()).String(), + Url: fmt.Sprintf("%s?apikey=%s", sourceUrl, config.GetInstance().GetAPIKey()), + } + processedMedia = append(processedMedia, HeresphereVideoMedia{ + Name: "direct stream", + Sources: []HeresphereVideoMediaSource{*processedEntry}, + }) + + resRatio := mediaFile.Width / mediaFile.Height + transcodeSize := config.GetInstance().GetMaxStreamingTranscodeSize() + transNames := []string{"MP4", "VP9", "HLS", "DASH"} + for i, trans := range []string{".mp4", ".webm", ".m3u8", ".mpd"} { + for _, res := range models.AllStreamingResolutionEnum { + maxTrans := transcodeSize.GetMaxResolution() + if height := res.GetMaxResolution(); (maxTrans == 0 || maxTrans >= height) && height <= mediaFile.Height { + processedEntry.Resolution = height + processedEntry.Height = height + processedEntry.Width = resRatio * height + processedEntry.Size = 80085 + if maxTrans == 0 { + processedEntry.Resolution = mediaFile.Height + processedEntry.Height = mediaFile.Height + processedEntry.Width = mediaFile.Width + processedEntry.Size = mediaFile.Size + } + processedEntry.Url = fmt.Sprintf("%s%s?resolution=%s&apikey=%s", sourceUrl, trans, res.String(), config.GetInstance().GetAPIKey()) + + // typeName := fmt.Sprintf("%s %s (%vp)", transNames[i], strings.ToLower(res.String()), height) + typeName := transNames[i] + mediaTypes[typeName] = append(mediaTypes[typeName], *processedEntry) + } + } } - typeName := fmt.Sprintf("%s %s", mediaFile.Format, mediaFile.VideoCodec) - mediaTypes[typeName] = append(mediaTypes[typeName], processedEntry) } + + // } } for codec, sources := range mediaTypes { From f770c29647f9b67febad12cd91c28e39eb2ccf56 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 02:09:54 +0200 Subject: [PATCH 023/144] Dont pointer for no reason --- internal/api/routes_heresphere.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 44ebbc51b71..ba973f19042 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -415,7 +415,7 @@ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) [ // for _, mediaFile := range file_ids { sourceUrl := urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamURL("").String() - processedEntry := &HeresphereVideoMediaSource{ + processedEntry := HeresphereVideoMediaSource{ Resolution: mediaFile.Height, Height: mediaFile.Height, Width: mediaFile.Width, @@ -424,7 +424,7 @@ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) [ } processedMedia = append(processedMedia, HeresphereVideoMedia{ Name: "direct stream", - Sources: []HeresphereVideoMediaSource{*processedEntry}, + Sources: []HeresphereVideoMediaSource{processedEntry}, }) resRatio := mediaFile.Width / mediaFile.Height @@ -448,7 +448,7 @@ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) [ // typeName := fmt.Sprintf("%s %s (%vp)", transNames[i], strings.ToLower(res.String()), height) typeName := transNames[i] - mediaTypes[typeName] = append(mediaTypes[typeName], *processedEntry) + mediaTypes[typeName] = append(mediaTypes[typeName], processedEntry) } } } From 77f66998d75bb562302209baf9d05744a3562462 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 02:11:34 +0200 Subject: [PATCH 024/144] Transcode check right potential zero value --- internal/api/routes_heresphere.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index ba973f19042..43860f3aaf4 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -438,7 +438,7 @@ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) [ processedEntry.Height = height processedEntry.Width = resRatio * height processedEntry.Size = 80085 - if maxTrans == 0 { + if height == 0 { processedEntry.Resolution = mediaFile.Height processedEntry.Height = mediaFile.Height processedEntry.Width = mediaFile.Width From f0b166c3792e51f3cf070c356569f64c1a38d9f7 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 02:14:41 +0200 Subject: [PATCH 025/144] /scan is fast enough to work, but not to make stash not 500 SLOW SQL --- internal/api/routes_heresphere.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 43860f3aaf4..d85aa23b256 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -193,7 +193,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Head("/", rs.HeresphereIndex) r.Post("/auth", rs.HeresphereLoginToken) - r.Post("/scan", rs.HeresphereScan) + // r.Post("/scan", rs.HeresphereScan) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) From 2f5ffb24cee85f8735bc2b93a269254ac4b64964 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 02:28:57 +0200 Subject: [PATCH 026/144] Bigass locks and scan go bye --- internal/api/routes_heresphere.go | 273 ++++++++++++------------------ 1 file changed, 110 insertions(+), 163 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index d85aa23b256..626c44f5624 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -12,7 +12,6 @@ import ( "strings" "github.com/go-chi/chi" - "github.com/stashapp/stash/internal/api/loaders" "github.com/stashapp/stash/internal/api/urlbuilders" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" @@ -193,7 +192,6 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Head("/", rs.HeresphereIndex) r.Post("/auth", rs.HeresphereLoginToken) - // r.Post("/scan", rs.HeresphereScan) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -290,7 +288,13 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) []HeresphereVideoTag { processedTags := []HeresphereVideoTag{} - mark_ids, err := rs.repository.SceneMarker.FindBySceneID(r.Context(), scene.ID) + if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + return scene.LoadRelationships(ctx, rs.repository.Scene) + }); err != nil { + return processedTags + } + + mark_ids, err := rs.resolver.Scene().SceneMarkers(r.Context(), scene) if err == nil { for _, mark := range mark_ids { genTag := HeresphereVideoTag{ @@ -302,43 +306,37 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] } } - if scene.GalleryIDs.Loaded() { - gallery_ids, errs := loaders.From(r.Context()).GalleryByID.LoadAll(scene.GalleryIDs.List()) - if firstError(errs) == nil { - for _, gal := range gallery_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Gallery:%v", gal.GetTitle()), - } - processedTags = append(processedTags, genTag) + gallery_ids, err := rs.resolver.Scene().Galleries(r.Context(), scene) + if err == nil { + for _, gal := range gallery_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Gallery:%v", gal.GetTitle()), } + processedTags = append(processedTags, genTag) } } - if scene.TagIDs.Loaded() { - tag_ids, errs := loaders.From(r.Context()).TagByID.LoadAll(scene.TagIDs.List()) - if firstError(errs) == nil { - for _, tag := range tag_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Tag:%v", tag.Name), - } - processedTags = append(processedTags, genTag) + tag_ids, err := rs.resolver.Scene().Tags(r.Context(), scene) + if err == nil { + for _, tag := range tag_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Tag:%v", tag.Name), } + processedTags = append(processedTags, genTag) } } - if scene.PerformerIDs.Loaded() { - perf_ids, errs := loaders.From(r.Context()).PerformerByID.LoadAll(scene.PerformerIDs.List()) - if firstError(errs) == nil { - for _, perf := range perf_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Talent:%s", perf.Name), - } - processedTags = append(processedTags, genTag) + perf_ids, err := rs.resolver.Scene().Performers(r.Context(), scene) + if err == nil { + for _, perf := range perf_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Talent:%s", perf.Name), } + processedTags = append(processedTags, genTag) } } - /*movie_ids, err := rs.resolver.Scene().Movies(r.Context(), scene) + movie_ids, err := rs.resolver.Scene().Movies(r.Context(), scene) if err == nil { for _, movie := range movie_ids { if movie.Movie != nil { @@ -348,15 +346,31 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] processedTags = append(processedTags, genTag) } } - }*/ + } // stash_ids, err := rs.resolver.Scene().StashIds(r.Context(), scene) - if scene.StudioID != nil { - studio_id, err := loaders.From(r.Context()).StudioByID.Load(*scene.StudioID) - if err == nil && studio_id != nil { + studio_id, err := rs.resolver.Scene().Studio(r.Context(), scene) + if err == nil && studio_id != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Studio:%v", studio_id.Name.String), + } + processedTags = append(processedTags, genTag) + } + + exists, err := rs.resolver.Scene().Interactive(r.Context(), scene) + if err == nil && exists { + shouldAdd := true + for _, tag := range processedTags { + if strings.Contains(tag.Name, "Tag:Interactive") { + shouldAdd = false + break + } + } + + if shouldAdd { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Studio:%v", studio_id.Name.String), + Name: "Tag:Interactive", } processedTags = append(processedTags, genTag) } @@ -516,70 +530,6 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques } } -// TODO: Consider removing, manual scan is faster -func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { - var scenes []*models.Scene - processedScenes := []HeresphereVideoEntryShort{} - if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - var err error - - if scenes, err = rs.repository.Scene.All(ctx); err != nil { - return err - } - - // Processing each scene and creating a new list - for _, scene := range scenes { - if err = scene.LoadRelationships(ctx, rs.repository.Scene); err != nil { - continue - } - - // Perform the necessary processing on each scene - processedScene := HeresphereVideoEntryShort{ - Link: fmt.Sprintf("%s/heresphere/%v", - GetBaseURL(r), - scene.ID, - ), - Title: scene.GetTitle(), - DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60000.0, - Rating: 0.0, - Favorites: scene.OCounter, - Comments: 0, - IsFavorite: false, - Tags: rs.getVideoTags(r, scene), - } - if scene.Date != nil { - processedScene.DateReleased = scene.Date.Format("2006-01-02") - } - if scene.Rating != nil { - isFavorite := *scene.Rating > 85 - processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 - processedScene.IsFavorite = isFavorite - } - file := scene.Files.Primary() - if file != nil { - processedScene.Duration = handleFloat64Value(file.Duration * 1000.0) - } - - processedScenes = append(processedScenes, processedScene) - } - - return err - }); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Create a JSON encoder for the response writer - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - err := json.NewEncoder(w).Encode(processedScenes) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } -} - // Find VR modes from scene func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntry) { // Detect VR modes from tags @@ -671,81 +621,78 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re processedScene := HeresphereVideoEntry{} if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { var err error - if err = scene.LoadRelationships(ctx, rs.repository.Scene); err != nil { - return err - } - - processedScene = HeresphereVideoEntry{ - Access: HeresphereMember, - Title: scene.GetTitle(), - Description: scene.Details, - ThumbnailImage: fmt.Sprintf("%s?apikey=%v", - urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetScreenshotURL(), - config.GetInstance().GetAPIKey(), - ), - ThumbnailVideo: fmt.Sprintf("%s?apikey=%v", - urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamPreviewURL(), - config.GetInstance().GetAPIKey(), - ), - DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60000.0, - Rating: 0.0, - Favorites: scene.OCounter, - Comments: 0, - IsFavorite: false, - Projection: HeresphereProjectionPerspective, - Stereo: HeresphereStereoMono, - IsEyeSwapped: false, - Fov: 180.0, - Lens: HeresphereLensLinear, - CameraIPD: 6.5, - /*Hsp: fmt.Sprintf("%s/heresphere/%v/hsp?apikey=%v", - GetBaseURL(r), - scene.ID, - config.GetInstance().GetAPIKey(), - ),*/ - EventServer: fmt.Sprintf("%s/heresphere/%v/event?apikey=%v", - GetBaseURL(r), - scene.ID, - config.GetInstance().GetAPIKey(), - ), - Scripts: rs.getVideoScripts(r, scene), - Subtitles: rs.getVideoSubtitles(r, scene), - Tags: rs.getVideoTags(r, scene), - Media: []HeresphereVideoMedia{}, - WriteFavorite: false, - WriteRating: false, - WriteTags: false, - WriteHSP: false, - } - FindProjectionTags(scene, &processedScene) - - if user.NeedsMediaSource { - processedScene.Media = rs.getVideoMedia(r, scene) - } - if scene.Date != nil { - processedScene.DateReleased = scene.Date.Format("2006-01-02") - } - if scene.Rating != nil { - isFavorite := *scene.Rating > 85 - processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 - processedScene.IsFavorite = isFavorite - } - file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) - if err == nil && len(file_ids) > 0 { - processedScene.Duration = handleFloat64Value(file_ids[0].Duration * 1000.0) - } - + err = scene.LoadRelationships(ctx, rs.repository.Scene) return err }); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } + processedScene = HeresphereVideoEntry{ + Access: HeresphereMember, + Title: scene.GetTitle(), + Description: scene.Details, + ThumbnailImage: fmt.Sprintf("%s?apikey=%v", + urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetScreenshotURL(), + config.GetInstance().GetAPIKey(), + ), + ThumbnailVideo: fmt.Sprintf("%s?apikey=%v", + urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamPreviewURL(), + config.GetInstance().GetAPIKey(), + ), + DateAdded: scene.CreatedAt.Format("2006-01-02"), + Duration: 60000.0, + Rating: 0.0, + Favorites: scene.OCounter, + Comments: 0, + IsFavorite: false, + Projection: HeresphereProjectionPerspective, + Stereo: HeresphereStereoMono, + IsEyeSwapped: false, + Fov: 180.0, + Lens: HeresphereLensLinear, + CameraIPD: 6.5, + /*Hsp: fmt.Sprintf("%s/heresphere/%v/hsp?apikey=%v", + GetBaseURL(r), + scene.ID, + config.GetInstance().GetAPIKey(), + ),*/ + EventServer: fmt.Sprintf("%s/heresphere/%v/event?apikey=%v", + GetBaseURL(r), + scene.ID, + config.GetInstance().GetAPIKey(), + ), + Scripts: rs.getVideoScripts(r, scene), + Subtitles: rs.getVideoSubtitles(r, scene), + Tags: rs.getVideoTags(r, scene), + Media: []HeresphereVideoMedia{}, + WriteFavorite: false, + WriteRating: false, + WriteTags: false, + WriteHSP: false, + } + FindProjectionTags(scene, &processedScene) + + if user.NeedsMediaSource { + processedScene.Media = rs.getVideoMedia(r, scene) + } + if scene.Date != nil { + processedScene.DateReleased = scene.Date.Format("2006-01-02") + } + if scene.Rating != nil { + isFavorite := *scene.Rating > 85 + processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 + processedScene.IsFavorite = isFavorite + } + file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) + if err == nil && len(file_ids) > 0 { + processedScene.Duration = handleFloat64Value(file_ids[0].Duration * 1000.0) + } + // Create a JSON encoder for the response writer w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - err := json.NewEncoder(w).Encode(processedScene) + err = json.NewEncoder(w).Encode(processedScene) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return From 86dd3e162c5a1115e4d1acb7fa8a996c42b4bf75 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 02:40:25 +0200 Subject: [PATCH 027/144] Added TODO on event --- internal/api/routes_heresphere.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 626c44f5624..4eda97d53e2 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -256,6 +256,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h if err := txn.WithTxn(r.Context(), rs.repository.TxnManager, func(ctx context.Context) error { qb := rs.repository.Scene + // TODO: Broken rating := int((user.Rating / 5.0) * 100.0) scene.Rating = &rating // TODO: user.Hsp From fcae04abe466c709d541f83288145181c1642ba7 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:30:58 +0200 Subject: [PATCH 028/144] Fix lint and add float rating conversion --- internal/api/routes_heresphere.go | 151 +++++++++++++----------------- pkg/models/rating.go | 7 ++ 2 files changed, 70 insertions(+), 88 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 4eda97d53e2..30cb8ec1e63 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -206,21 +206,6 @@ func (rs heresphereRoutes) Routes() chi.Router { return r } -func heresphereHandler() func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c := config.GetInstance() - - if strings.Contains(r.UserAgent(), "HereSphere") && c.GetRedirectHeresphere() && (r.URL.Path == "/" || strings.HasPrefix(r.URL.Path, "/login")) { - http.Redirect(w, r, "/heresphere", http.StatusSeeOther) - return - } - - next.ServeHTTP(w, r) - }) - } -} - func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { event := HeresphereVideoEvent{} if err := json.NewDecoder(r.Body).Decode(&event); err != nil { @@ -244,7 +229,6 @@ func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.R } func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Request) { - // TODO: This w.WriteHeader(http.StatusNotImplemented) } @@ -256,11 +240,8 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h if err := txn.WithTxn(r.Context(), rs.repository.TxnManager, func(ctx context.Context) error { qb := rs.repository.Scene - // TODO: Broken - rating := int((user.Rating / 5.0) * 100.0) + rating := models.Rating5To100F(user.Rating) scene.Rating = &rating - // TODO: user.Hsp - // TODO: user.Tags crosscheck if user.DeleteFile { qe := rs.repository.File @@ -359,24 +340,6 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] processedTags = append(processedTags, genTag) } - exists, err := rs.resolver.Scene().Interactive(r.Context(), scene) - if err == nil && exists { - shouldAdd := true - for _, tag := range processedTags { - if strings.Contains(tag.Name, "Tag:Interactive") { - shouldAdd = false - break - } - } - - if shouldAdd { - genTag := HeresphereVideoTag{ - Name: "Tag:Interactive", - } - processedTags = append(processedTags, genTag) - } - } - return processedTags } @@ -390,7 +353,7 @@ func (rs heresphereRoutes) getVideoScripts(r *http.Request, scene *models.Scene) urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetFunscriptURL(), config.GetInstance().GetAPIKey(), ), - Rating: 4.2, + Rating: 5, } processedScripts = append(processedScripts, processedScript) } @@ -621,9 +584,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re processedScene := HeresphereVideoEntry{} if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - var err error - err = scene.LoadRelationships(ctx, rs.repository.Scene) - return err + return scene.LoadRelationships(ctx, rs.repository.Scene) }); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -643,7 +604,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re ), DateAdded: scene.CreatedAt.Format("2006-01-02"), Duration: 60000.0, - Rating: 0.0, + Rating: 0, Favorites: scene.OCounter, Comments: 0, IsFavorite: false, @@ -668,7 +629,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Tags: rs.getVideoTags(r, scene), Media: []HeresphereVideoMedia{}, WriteFavorite: false, - WriteRating: false, + WriteRating: true, WriteTags: false, WriteHSP: false, } @@ -681,19 +642,20 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re processedScene.DateReleased = scene.Date.Format("2006-01-02") } if scene.Rating != nil { - isFavorite := *scene.Rating > 85 - processedScene.Rating = float32(*scene.Rating) * 0.05 // 0-5 - processedScene.IsFavorite = isFavorite + fiveScale := models.Rating100To5F(*scene.Rating) + processedScene.Rating = fiveScale + processedScene.IsFavorite = fiveScale >= 4 } - file_ids, err := rs.resolver.Scene().Files(r.Context(), scene) - if err == nil && len(file_ids) > 0 { - processedScene.Duration = handleFloat64Value(file_ids[0].Duration * 1000.0) + + file_ids := scene.Files.Primary() + if file_ids != nil { + processedScene.Duration = handleFloat64Value(file_ids.Duration * 1000.0) } // Create a JSON encoder for the response writer w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - err = json.NewEncoder(w).Encode(processedScene) + err := json.NewEncoder(w).Encode(processedScene) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -737,43 +699,6 @@ func (rs heresphereRoutes) HeresphereLoginToken(w http.ResponseWriter, r *http.R } } -// TODO: This is a copy of the Ctx from routes_scene -// Create a general version -func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) - if err != nil { - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - - var scene *models.Scene - _ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - qb := rs.sceneFinder - scene, _ = qb.Find(ctx, sceneID) - - if scene != nil { - if err := scene.LoadPrimaryFile(ctx, rs.fileFinder); err != nil { - if !errors.Is(err, context.Canceled) { - logger.Errorf("error loading primary file for scene %d: %v", sceneID, err) - } - // set scene to nil so that it doesn't try to use the primary file - scene = nil - } - } - - return nil - }) - if scene == nil { - http.Error(w, http.StatusText(404), 404) - return - } - - ctx := context.WithValue(r.Context(), heresphereKey, scene) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - func HeresphereHasValidToken(r *http.Request) bool { apiKey := r.Header.Get(HeresphereAuthHeader) @@ -814,6 +739,56 @@ func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { } } +func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) + if err != nil { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + var scene *models.Scene + _ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + qb := rs.sceneFinder + scene, _ = qb.Find(ctx, sceneID) + + if scene != nil { + if err := scene.LoadPrimaryFile(ctx, rs.fileFinder); err != nil { + if !errors.Is(err, context.Canceled) { + logger.Errorf("error loading primary file for scene %d: %v", sceneID, err) + } + // set scene to nil so that it doesn't try to use the primary file + scene = nil + } + } + + return nil + }) + if scene == nil { + http.Error(w, http.StatusText(404), 404) + return + } + + ctx := context.WithValue(r.Context(), heresphereKey, scene) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func heresphereHandler() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c := config.GetInstance() + + if strings.Contains(r.UserAgent(), "HereSphere") && c.GetRedirectHeresphere() && (r.URL.Path == "/" || strings.HasPrefix(r.URL.Path, "/login")) { + http.Redirect(w, r, "/heresphere", http.StatusSeeOther) + return + } + + next.ServeHTTP(w, r) + }) + } +} + func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} diff --git a/pkg/models/rating.go b/pkg/models/rating.go index 66219b50a62..95a676de393 100644 --- a/pkg/models/rating.go +++ b/pkg/models/rating.go @@ -62,8 +62,15 @@ func Rating100To5(rating100 int) int { val := math.Round((float64(rating100) / 20)) return int(math.Max(minRating5, math.Min(maxRating5, val))) } +func Rating100To5F(rating100 int) float32 { + val := math.Round((float64(rating100) / 20.0)) + return float32(math.Max(minRating5, math.Min(maxRating5, val))) +} // Rating5To100 converts a 1-5 rating to a 1-100 rating func Rating5To100(rating5 int) int { return int(math.Max(minRating100, math.Min(maxRating100, float64(rating5*20)))) } +func Rating5To100F(rating5 float32) int { + return int(math.Max(minRating100, math.Min(maxRating100, float64(rating5*20.0)))) +} From 5a705c9764706f2c79a1afcaf59ca8f943d346a7 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:51:32 +0200 Subject: [PATCH 029/144] Cleanup --- internal/api/routes_heresphere.go | 175 +++++++++++++++++++----------- 1 file changed, 114 insertions(+), 61 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 30cb8ec1e63..00627ba73c6 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -182,6 +182,9 @@ type heresphereRoutes struct { resolver ResolverRoot } +/* + * This function provides the possible routes for this api. + */ func (rs heresphereRoutes) Routes() chi.Router { r := chi.NewRouter() @@ -206,6 +209,11 @@ func (rs heresphereRoutes) Routes() chi.Router { return r } +/* + * This is a video playback event + * Intended for server-sided script playback. + * But since we dont need that, we just use it for timestamps. + */ func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { event := HeresphereVideoEvent{} if err := json.NewDecoder(r.Body).Decode(&event); err != nil { @@ -228,10 +236,17 @@ func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.R w.WriteHeader(http.StatusOK) } +/* + * HSP is a HereSphere config file + * It stores the players local config such as projection or color settings etc. + */ func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } +/* + * This endpoint is for letting the user update scene data + */ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(heresphereKey).(*models.Scene) user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) @@ -267,6 +282,9 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h w.WriteHeader(http.StatusOK) } +/* + * This auxillary function gathers various tags from the scene to feed the api. + */ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) []HeresphereVideoTag { processedTags := []HeresphereVideoTag{} @@ -343,6 +361,9 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] return processedTags } +/* + * This auxillary function gathers a script if applicable + */ func (rs heresphereRoutes) getVideoScripts(r *http.Request, scene *models.Scene) []HeresphereVideoScript { processedScripts := []HeresphereVideoScript{} @@ -360,6 +381,10 @@ func (rs heresphereRoutes) getVideoScripts(r *http.Request, scene *models.Scene) return processedScripts } + +/* + * This auxillary function gathers subtitles if applicable + */ func (rs heresphereRoutes) getVideoSubtitles(r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { processedSubtitles := []HeresphereVideoSubtitle{} @@ -381,58 +406,57 @@ func (rs heresphereRoutes) getVideoSubtitles(r *http.Request, scene *models.Scen return processedSubtitles } + +/* + * This auxillary function gathers media information + transcoding options. + */ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) []HeresphereVideoMedia { processedMedia := []HeresphereVideoMedia{} mediaTypes := make(map[string][]HeresphereVideoMediaSource) - // if file_ids, err := rs.resolver.Scene().Files(r.Context(), scene); err == nil { - if err := scene.LoadPrimaryFile(r.Context(), rs.repository.File); err == nil { - if mediaFile := scene.Files.Primary(); mediaFile != nil { - - // for _, mediaFile := range file_ids { - - sourceUrl := urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamURL("").String() - processedEntry := HeresphereVideoMediaSource{ - Resolution: mediaFile.Height, - Height: mediaFile.Height, - Width: mediaFile.Width, - Size: mediaFile.Size, - Url: fmt.Sprintf("%s?apikey=%s", sourceUrl, config.GetInstance().GetAPIKey()), - } - processedMedia = append(processedMedia, HeresphereVideoMedia{ - Name: "direct stream", - Sources: []HeresphereVideoMediaSource{processedEntry}, - }) - - resRatio := mediaFile.Width / mediaFile.Height - transcodeSize := config.GetInstance().GetMaxStreamingTranscodeSize() - transNames := []string{"MP4", "VP9", "HLS", "DASH"} - for i, trans := range []string{".mp4", ".webm", ".m3u8", ".mpd"} { - for _, res := range models.AllStreamingResolutionEnum { - maxTrans := transcodeSize.GetMaxResolution() - if height := res.GetMaxResolution(); (maxTrans == 0 || maxTrans >= height) && height <= mediaFile.Height { - processedEntry.Resolution = height - processedEntry.Height = height - processedEntry.Width = resRatio * height - processedEntry.Size = 80085 - if height == 0 { - processedEntry.Resolution = mediaFile.Height - processedEntry.Height = mediaFile.Height - processedEntry.Width = mediaFile.Width - processedEntry.Size = mediaFile.Size + if file_ids, err := rs.resolver.Scene().Files(r.Context(), scene); err == nil { + for _, mediaFile := range file_ids { + if mediaFile.ID == scene.PrimaryFileID.String() { + sourceUrl := urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamURL("").String() + processedEntry := HeresphereVideoMediaSource{ + Resolution: mediaFile.Height, + Height: mediaFile.Height, + Width: mediaFile.Width, + Size: mediaFile.Size, + Url: fmt.Sprintf("%s?apikey=%s", sourceUrl, config.GetInstance().GetAPIKey()), + } + processedMedia = append(processedMedia, HeresphereVideoMedia{ + Name: "direct stream", + Sources: []HeresphereVideoMediaSource{processedEntry}, + }) + + resRatio := mediaFile.Width / mediaFile.Height + transcodeSize := config.GetInstance().GetMaxStreamingTranscodeSize() + transNames := []string{"HLS", "DASH"} + for i, trans := range []string{".m3u8", ".mpd"} { + for _, res := range models.AllStreamingResolutionEnum { + maxTrans := transcodeSize.GetMaxResolution() + if height := res.GetMaxResolution(); (maxTrans == 0 || maxTrans >= height) && height <= mediaFile.Height { + processedEntry.Resolution = height + processedEntry.Height = height + processedEntry.Width = resRatio * height + processedEntry.Size = 0 + if height == 0 { + processedEntry.Resolution = mediaFile.Height + processedEntry.Height = mediaFile.Height + processedEntry.Width = mediaFile.Width + processedEntry.Size = mediaFile.Size + } + processedEntry.Url = fmt.Sprintf("%s%s?resolution=%s&apikey=%s", sourceUrl, trans, res.String(), config.GetInstance().GetAPIKey()) + + typeName := transNames[i] + mediaTypes[typeName] = append(mediaTypes[typeName], processedEntry) } - processedEntry.Url = fmt.Sprintf("%s%s?resolution=%s&apikey=%s", sourceUrl, trans, res.String(), config.GetInstance().GetAPIKey()) - - // typeName := fmt.Sprintf("%s %s (%vp)", transNames[i], strings.ToLower(res.String()), height) - typeName := transNames[i] - mediaTypes[typeName] = append(mediaTypes[typeName], processedEntry) } } } } - - // } } for codec, sources := range mediaTypes { @@ -445,6 +469,9 @@ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) [ return processedMedia } +/* + * This endpoint provides the main libraries that are available to browse. + */ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { banner := HeresphereBanner{ Image: fmt.Sprintf("%s%s", @@ -458,7 +485,7 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques } var scenes []*models.Scene - if err := rs.repository.WithTxn(r.Context(), func(ctx context.Context) error { + if err := txn.WithReadTxn(r.Context(), rs.repository.TxnManager, func(ctx context.Context) error { var err error scenes, err = rs.repository.Scene.All(ctx) return err @@ -494,7 +521,9 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques } } -// Find VR modes from scene +/* + * This auxillary function finds vr projection modes from tags and the filename. + */ func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntry) { // Detect VR modes from tags for _, tag := range processedScene.Tags { @@ -573,6 +602,9 @@ func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntr } } +/* + * This endpoint provides a single scenes full information. + */ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) if user.Tags != nil { @@ -614,11 +646,11 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Fov: 180.0, Lens: HeresphereLensLinear, CameraIPD: 6.5, - /*Hsp: fmt.Sprintf("%s/heresphere/%v/hsp?apikey=%v", + Hsp: fmt.Sprintf("%s/heresphere/%v/hsp?apikey=%v", GetBaseURL(r), scene.ID, config.GetInstance().GetAPIKey(), - ),*/ + ), EventServer: fmt.Sprintf("%s/heresphere/%v/event?apikey=%v", GetBaseURL(r), scene.ID, @@ -662,6 +694,9 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } } +/* + * This auxillary function finds if a login is needed, and auth is correct. + */ func basicLogin(username string, password string) bool { if config.GetInstance().HasCredentials() { err := manager.GetInstance().SessionStore.LoginPlain(username, password) @@ -670,6 +705,9 @@ func basicLogin(username string, password string) bool { return false } +/* + * This endpoint function allows the user to login and receive a token if successful. + */ func (rs heresphereRoutes) HeresphereLoginToken(w http.ResponseWriter, r *http.Request) { user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) @@ -699,6 +737,9 @@ func (rs heresphereRoutes) HeresphereLoginToken(w http.ResponseWriter, r *http.R } } +/* + * This auxillary function finds if the request has a valid auth token. + */ func HeresphereHasValidToken(r *http.Request) bool { apiKey := r.Header.Get(HeresphereAuthHeader) @@ -709,6 +750,9 @@ func HeresphereHasValidToken(r *http.Request) bool { return len(apiKey) > 0 && apiKey == config.GetInstance().GetAPIKey() } +/* + * This auxillary writes a library with a fake name upon auth failure + */ func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { banner := HeresphereBanner{ Image: fmt.Sprintf("%s%s", @@ -739,6 +783,27 @@ func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { } } +/* + * This http handler redirects HereSphere if enabled + */ +func heresphereHandler() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c := config.GetInstance() + + if strings.Contains(r.UserAgent(), "HereSphere") && c.GetRedirectHeresphere() && (r.URL.Path == "/" || strings.HasPrefix(r.URL.Path, "/login")) { + http.Redirect(w, r, "/heresphere", http.StatusSeeOther) + return + } + + next.ServeHTTP(w, r) + }) + } +} + +/* + * This context function finds the applicable scene from the request and stores it. + */ func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) @@ -774,21 +839,9 @@ func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { }) } -func heresphereHandler() func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c := config.GetInstance() - - if strings.Contains(r.UserAgent(), "HereSphere") && c.GetRedirectHeresphere() && (r.URL.Path == "/" || strings.HasPrefix(r.URL.Path, "/login")) { - http.Redirect(w, r, "/heresphere", http.StatusSeeOther) - return - } - - next.ServeHTTP(w, r) - }) - } -} - +/* + * This context function finds if the authentication is correct, otherwise rejects the request. + */ func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} From 0ea4136b9cb83d8a3741b452a695828525d39745 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 14:20:16 +0200 Subject: [PATCH 030/144] Fix misspell --- internal/api/routes_heresphere.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 00627ba73c6..ac04334f5a9 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -283,7 +283,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h } /* - * This auxillary function gathers various tags from the scene to feed the api. + * This auxiliary function gathers various tags from the scene to feed the api. */ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) []HeresphereVideoTag { processedTags := []HeresphereVideoTag{} @@ -362,7 +362,7 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] } /* - * This auxillary function gathers a script if applicable + * This auxiliary function gathers a script if applicable */ func (rs heresphereRoutes) getVideoScripts(r *http.Request, scene *models.Scene) []HeresphereVideoScript { processedScripts := []HeresphereVideoScript{} @@ -383,7 +383,7 @@ func (rs heresphereRoutes) getVideoScripts(r *http.Request, scene *models.Scene) } /* - * This auxillary function gathers subtitles if applicable + * This auxiliary function gathers subtitles if applicable */ func (rs heresphereRoutes) getVideoSubtitles(r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { processedSubtitles := []HeresphereVideoSubtitle{} @@ -408,7 +408,7 @@ func (rs heresphereRoutes) getVideoSubtitles(r *http.Request, scene *models.Scen } /* - * This auxillary function gathers media information + transcoding options. + * This auxiliary function gathers media information + transcoding options. */ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) []HeresphereVideoMedia { processedMedia := []HeresphereVideoMedia{} @@ -522,7 +522,7 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques } /* - * This auxillary function finds vr projection modes from tags and the filename. + * This auxiliary function finds vr projection modes from tags and the filename. */ func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntry) { // Detect VR modes from tags @@ -695,7 +695,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } /* - * This auxillary function finds if a login is needed, and auth is correct. + * This auxiliary function finds if a login is needed, and auth is correct. */ func basicLogin(username string, password string) bool { if config.GetInstance().HasCredentials() { @@ -738,7 +738,7 @@ func (rs heresphereRoutes) HeresphereLoginToken(w http.ResponseWriter, r *http.R } /* - * This auxillary function finds if the request has a valid auth token. + * This auxiliary function finds if the request has a valid auth token. */ func HeresphereHasValidToken(r *http.Request) bool { apiKey := r.Header.Get(HeresphereAuthHeader) @@ -751,7 +751,7 @@ func HeresphereHasValidToken(r *http.Request) bool { } /* - * This auxillary writes a library with a fake name upon auth failure + * This auxiliary writes a library with a fake name upon auth failure */ func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { banner := HeresphereBanner{ From 78700bd5f821e525ae068504fa0a465392699820 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 14:29:54 +0200 Subject: [PATCH 031/144] Add missing --- internal/api/routes_heresphere.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index ac04334f5a9..0da6470361f 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -194,6 +194,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Get("/", rs.HeresphereIndex) r.Head("/", rs.HeresphereIndex) + r.Post("/scan", rs.HeresphereScan) r.Post("/auth", rs.HeresphereLoginToken) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -244,6 +245,13 @@ func (rs heresphereRoutes) HeresphereVideoHsp(w http.ResponseWriter, r *http.Req w.WriteHeader(http.StatusNotImplemented) } +/* + * This endpoint provides a list of all videos in a short format + */ +func (rs heresphereRoutes) HeresphereScan(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + /* * This endpoint is for letting the user update scene data */ @@ -348,8 +356,6 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] } } - // stash_ids, err := rs.resolver.Scene().StashIds(r.Context(), scene) - studio_id, err := rs.resolver.Scene().Studio(r.Context(), scene) if err == nil && studio_id != nil { genTag := HeresphereVideoTag{ From f89aff90e29861ec41b9abf094fbd1a03a772219 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 14:56:18 +0200 Subject: [PATCH 032/144] Nicer apikey add --- internal/api/routes_heresphere.go | 86 +++++++++++++++++-------------- internal/api/urlbuilders/scene.go | 4 ++ 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 0da6470361f..0c44ff0c468 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strconv" "strings" @@ -375,11 +376,8 @@ func (rs heresphereRoutes) getVideoScripts(r *http.Request, scene *models.Scene) if interactive, err := rs.resolver.Scene().Interactive(r.Context(), scene); err == nil && interactive { processedScript := HeresphereVideoScript{ - Name: "Default script", - Url: fmt.Sprintf("%s?apikey=%v", - urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetFunscriptURL(), - config.GetInstance().GetAPIKey(), - ), + Name: "Default script", + Url: addApiKey(urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetFunscriptURL()), Rating: 5, } processedScripts = append(processedScripts, processedScript) @@ -399,12 +397,11 @@ func (rs heresphereRoutes) getVideoSubtitles(r *http.Request, scene *models.Scen processedCaption := HeresphereVideoSubtitle{ Name: caption.Filename, Language: caption.LanguageCode, - Url: fmt.Sprintf("%s?lang=%v&type=%v&apikey=%v", + Url: addApiKey(fmt.Sprintf("%s?lang=%v&type=%v", urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetCaptionURL(), caption.LanguageCode, caption.CaptionType, - config.GetInstance().GetAPIKey(), - ), + )), } processedSubtitles = append(processedSubtitles, processedCaption) } @@ -430,7 +427,7 @@ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) [ Height: mediaFile.Height, Width: mediaFile.Width, Size: mediaFile.Size, - Url: fmt.Sprintf("%s?apikey=%s", sourceUrl, config.GetInstance().GetAPIKey()), + Url: addApiKey(sourceUrl), } processedMedia = append(processedMedia, HeresphereVideoMedia{ Name: "direct stream", @@ -454,7 +451,7 @@ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) [ processedEntry.Width = mediaFile.Width processedEntry.Size = mediaFile.Size } - processedEntry.Url = fmt.Sprintf("%s%s?resolution=%s&apikey=%s", sourceUrl, trans, res.String(), config.GetInstance().GetAPIKey()) + processedEntry.Url = addApiKey(fmt.Sprintf("%s%s?resolution=%s", sourceUrl, trans, res.String())) typeName := transNames[i] mediaTypes[typeName] = append(mediaTypes[typeName], processedEntry) @@ -629,39 +626,31 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re } processedScene = HeresphereVideoEntry{ - Access: HeresphereMember, - Title: scene.GetTitle(), - Description: scene.Details, - ThumbnailImage: fmt.Sprintf("%s?apikey=%v", - urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetScreenshotURL(), - config.GetInstance().GetAPIKey(), - ), - ThumbnailVideo: fmt.Sprintf("%s?apikey=%v", - urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamPreviewURL(), - config.GetInstance().GetAPIKey(), - ), - DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60000.0, - Rating: 0, - Favorites: scene.OCounter, - Comments: 0, - IsFavorite: false, - Projection: HeresphereProjectionPerspective, - Stereo: HeresphereStereoMono, - IsEyeSwapped: false, - Fov: 180.0, - Lens: HeresphereLensLinear, - CameraIPD: 6.5, - Hsp: fmt.Sprintf("%s/heresphere/%v/hsp?apikey=%v", + Access: HeresphereMember, + Title: scene.GetTitle(), + Description: scene.Details, + ThumbnailImage: addApiKey(urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetScreenshotURL()), + ThumbnailVideo: addApiKey(urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamPreviewURL()), + DateAdded: scene.CreatedAt.Format("2006-01-02"), + Duration: 60000.0, + Rating: 0, + Favorites: scene.OCounter, + Comments: 0, + IsFavorite: false, + Projection: HeresphereProjectionPerspective, + Stereo: HeresphereStereoMono, + IsEyeSwapped: false, + Fov: 180.0, + Lens: HeresphereLensLinear, + CameraIPD: 6.5, + Hsp: addApiKey(fmt.Sprintf("%s/heresphere/%v/hsp", GetBaseURL(r), scene.ID, - config.GetInstance().GetAPIKey(), - ), - EventServer: fmt.Sprintf("%s/heresphere/%v/event?apikey=%v", + )), + EventServer: addApiKey(fmt.Sprintf("%s/heresphere/%v/event", GetBaseURL(r), scene.ID, - config.GetInstance().GetAPIKey(), - ), + )), Scripts: rs.getVideoScripts(r, scene), Subtitles: rs.getVideoSubtitles(r, scene), Tags: rs.getVideoTags(r, scene), @@ -756,6 +745,25 @@ func HeresphereHasValidToken(r *http.Request) bool { return len(apiKey) > 0 && apiKey == config.GetInstance().GetAPIKey() } +/* + * This auxiliary function adds an auth token to a url + */ +func addApiKey(urlS string) string { + u, err := url.Parse(urlS) + if err != nil { + // shouldn't happen + panic(err) + } + + if config.GetInstance().GetAPIKey() != "" { + v := u.Query() + v.Set("apikey", config.GetInstance().GetAPIKey()) + u.RawQuery = v.Encode() + } + + return u.String() +} + /* * This auxiliary writes a library with a fake name upon auth failure */ diff --git a/internal/api/urlbuilders/scene.go b/internal/api/urlbuilders/scene.go index c70feafe753..e7a9db71fbe 100644 --- a/internal/api/urlbuilders/scene.go +++ b/internal/api/urlbuilders/scene.go @@ -72,3 +72,7 @@ func (b SceneURLBuilder) GetCaptionURL() string { func (b SceneURLBuilder) GetInteractiveHeatmapURL() string { return b.BaseURL + "/scene/" + b.SceneID + "/interactive_heatmap" } + +func (b SceneURLBuilder) GetHereSphereURL() string { + return b.BaseURL + "/heresphere/" + b.SceneID +} From dfbf499b7d41727a2ec2b49160351b431fc35a2c Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 15:02:34 +0200 Subject: [PATCH 033/144] Unecessary --- internal/api/urlbuilders/scene.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/api/urlbuilders/scene.go b/internal/api/urlbuilders/scene.go index e7a9db71fbe..c70feafe753 100644 --- a/internal/api/urlbuilders/scene.go +++ b/internal/api/urlbuilders/scene.go @@ -72,7 +72,3 @@ func (b SceneURLBuilder) GetCaptionURL() string { func (b SceneURLBuilder) GetInteractiveHeatmapURL() string { return b.BaseURL + "/scene/" + b.SceneID + "/interactive_heatmap" } - -func (b SceneURLBuilder) GetHereSphereURL() string { - return b.BaseURL + "/heresphere/" + b.SceneID -} From 99839a80fd372dee9a18ad9991c82622a187d51a Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 16:44:38 +0200 Subject: [PATCH 034/144] Fix remaining heresphere bugs --- internal/api/routes_heresphere.go | 101 ++++++++++++++++-------------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 0c44ff0c468..8411f2ad684 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -117,8 +117,8 @@ type HeresphereVideoEntry struct { Description string `json:"description"` ThumbnailImage string `json:"thumbnailImage"` ThumbnailVideo string `json:"thumbnailVideo,omitempty"` - DateReleased string `json:"dateReleased"` - DateAdded string `json:"dateAdded"` + DateReleased string `json:"dateReleased,omitempty"` + DateAdded string `json:"dateAdded,omitempty"` Duration float64 `json:"duration,omitempty"` Rating float32 `json:"rating,omitempty"` Favorites int `json:"favorites"` @@ -144,8 +144,8 @@ type HeresphereVideoEntry struct { type HeresphereVideoEntryShort struct { Link string `json:"link"` Title string `json:"title"` - DateReleased string `json:"dateReleased"` - DateAdded string `json:"dateAdded"` + DateReleased string `json:"dateReleased,omitempty"` + DateAdded string `json:"dateAdded,omitempty"` Duration float64 `json:"duration,omitempty"` Rating float32 `json:"rating,omitempty"` Favorites int `json:"favorites"` @@ -195,7 +195,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Get("/", rs.HeresphereIndex) r.Head("/", rs.HeresphereIndex) - r.Post("/scan", rs.HeresphereScan) + // r.Post("/scan", rs.HeresphereScan) r.Post("/auth", rs.HeresphereLoginToken) r.Route("/{sceneId}", func(r chi.Router) { r.Use(rs.HeresphereSceneCtx) @@ -203,7 +203,7 @@ func (rs heresphereRoutes) Routes() chi.Router { r.Post("/", rs.HeresphereVideoData) r.Get("/", rs.HeresphereVideoData) - r.Get("/hsp", rs.HeresphereVideoHsp) + // r.Get("/hsp", rs.HeresphereVideoHsp) r.Post("/event", rs.HeresphereVideoEvent) }) }) @@ -365,6 +365,14 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] processedTags = append(processedTags, genTag) } + interactive, err := rs.resolver.Scene().Interactive(r.Context(), scene) + if err == nil && interactive { + genTag := HeresphereVideoTag{ + Name: "Interactive", + } + processedTags = append(processedTags, genTag) + } + return processedTags } @@ -418,45 +426,46 @@ func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) [ mediaTypes := make(map[string][]HeresphereVideoMediaSource) - if file_ids, err := rs.resolver.Scene().Files(r.Context(), scene); err == nil { - for _, mediaFile := range file_ids { - if mediaFile.ID == scene.PrimaryFileID.String() { - sourceUrl := urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamURL("").String() - processedEntry := HeresphereVideoMediaSource{ - Resolution: mediaFile.Height, - Height: mediaFile.Height, - Width: mediaFile.Width, - Size: mediaFile.Size, - Url: addApiKey(sourceUrl), - } - processedMedia = append(processedMedia, HeresphereVideoMedia{ - Name: "direct stream", - Sources: []HeresphereVideoMediaSource{processedEntry}, - }) - - resRatio := mediaFile.Width / mediaFile.Height - transcodeSize := config.GetInstance().GetMaxStreamingTranscodeSize() - transNames := []string{"HLS", "DASH"} - for i, trans := range []string{".m3u8", ".mpd"} { - for _, res := range models.AllStreamingResolutionEnum { - maxTrans := transcodeSize.GetMaxResolution() - if height := res.GetMaxResolution(); (maxTrans == 0 || maxTrans >= height) && height <= mediaFile.Height { - processedEntry.Resolution = height - processedEntry.Height = height - processedEntry.Width = resRatio * height - processedEntry.Size = 0 - if height == 0 { - processedEntry.Resolution = mediaFile.Height - processedEntry.Height = mediaFile.Height - processedEntry.Width = mediaFile.Width - processedEntry.Size = mediaFile.Size - } - processedEntry.Url = addApiKey(fmt.Sprintf("%s%s?resolution=%s", sourceUrl, trans, res.String())) - - typeName := transNames[i] - mediaTypes[typeName] = append(mediaTypes[typeName], processedEntry) - } + if err := txn.WithTxn(r.Context(), rs.repository.TxnManager, func(ctx context.Context) error { + return scene.LoadPrimaryFile(r.Context(), rs.repository.File) + }); err != nil { + return processedMedia + } + + if mediaFile := scene.Files.Primary(); mediaFile != nil { + sourceUrl := urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamURL("").String() + processedEntry := HeresphereVideoMediaSource{ + Resolution: mediaFile.Height, + Height: mediaFile.Height, + Width: mediaFile.Width, + Size: mediaFile.Size, + Url: addApiKey(sourceUrl), + } + processedMedia = append(processedMedia, HeresphereVideoMedia{ + Name: "direct stream", + Sources: []HeresphereVideoMediaSource{processedEntry}, + }) + + resRatio := mediaFile.Width / mediaFile.Height + transcodeSize := config.GetInstance().GetMaxStreamingTranscodeSize() + transNames := []string{"HLS", "DASH"} + for i, trans := range []string{".m3u8", ".mpd"} { + for _, res := range models.AllStreamingResolutionEnum { + maxTrans := transcodeSize.GetMaxResolution() + if height := res.GetMaxResolution(); (maxTrans == 0 || maxTrans >= height) && height <= mediaFile.Height { + processedEntry.Resolution = height + processedEntry.Height = height + processedEntry.Width = resRatio * height + processedEntry.Size = 0 + if height == 0 { + processedEntry.Resolution = mediaFile.Height + processedEntry.Height = mediaFile.Height + processedEntry.Width = mediaFile.Width } + processedEntry.Url = addApiKey(fmt.Sprintf("%s%s?resolution=%s", sourceUrl, trans, res.String())) + + typeName := transNames[i] + mediaTypes[typeName] = append(mediaTypes[typeName], processedEntry) } } } @@ -643,10 +652,6 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Fov: 180.0, Lens: HeresphereLensLinear, CameraIPD: 6.5, - Hsp: addApiKey(fmt.Sprintf("%s/heresphere/%v/hsp", - GetBaseURL(r), - scene.ID, - )), EventServer: addApiKey(fmt.Sprintf("%s/heresphere/%v/event", GetBaseURL(r), scene.ID, From 1056b87671ad10952bb51f6c3ae5fc59387b5d39 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 17:21:50 +0200 Subject: [PATCH 035/144] Changed redirect to just a optional button --- graphql/documents/data/config.graphql | 2 +- graphql/schema/types/config.graphql | 4 ++-- internal/api/resolver_mutation_configure.go | 2 +- internal/api/resolver_query_configuration.go | 4 ++-- internal/api/routes_heresphere.go | 18 ------------------ internal/api/server.go | 1 - internal/manager/config/config.go | 10 +++++----- ui/v2.5/src/components/MainNavbar.tsx | 16 ++++++++++++++++ .../SettingsInterfacePanel.tsx | 10 +++++----- ui/v2.5/src/locales/en-GB.json | 6 +++--- 10 files changed, 35 insertions(+), 38 deletions(-) diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index b7228ec90f3..2278537e461 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -93,7 +93,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult { } handyKey funscriptOffset - redirectHeresphere + showHeresphereButton } fragment ConfigDLNAData on ConfigDLNAResult { diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 9cefc2c90f6..5ea3ee841b8 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -359,7 +359,7 @@ input ConfigInterfaceInput { """True if we should send notifications to the desktop""" notificationsEnabled: Boolean """True if we should automatically redirect Heresphere clients to the Heresphere endpoint""" - redirectHeresphere: Boolean + showHeresphereButton: Boolean } type ConfigDisableDropdownCreate { @@ -390,7 +390,7 @@ type ConfigInterfaceResult { """True if we should send desktop notifications""" notificationsEnabled: Boolean """True if we should automatically redirect Heresphere clients to the Heresphere endpoint""" - redirectHeresphere: Boolean + showHeresphereButton: Boolean """If true, video will autostart on load in the scene player""" autostartVideo: Boolean """If true, video will autostart when loading from play random or play selected""" diff --git a/internal/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index 5028d7cf326..b2138a239a3 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -404,7 +404,7 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI setBool(config.NoBrowser, input.NoBrowser) setBool(config.NotificationsEnabled, input.NotificationsEnabled) - setBool(config.RedirectHeresphere, input.RedirectHeresphere) + setBool(config.ShowHeresphereButton, input.ShowHeresphereButton) setBool(config.ShowScrubber, input.ShowScrubber) diff --git a/internal/api/resolver_query_configuration.go b/internal/api/resolver_query_configuration.go index 8e442b1ec79..a6712eadf9d 100644 --- a/internal/api/resolver_query_configuration.go +++ b/internal/api/resolver_query_configuration.go @@ -159,7 +159,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult { language := config.GetLanguage() handyKey := config.GetHandyKey() scriptOffset := config.GetFunscriptOffset() - redirectHeresphere := config.GetRedirectHeresphere() + showHeresphereButton := config.GetShowHeresphereButton() imageLightboxOptions := config.GetImageLightboxOptions() // FIXME - misnamed output field means we have redundant fields disableDropdownCreate := config.GetDisableDropdownCreate() @@ -194,7 +194,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult { HandyKey: &handyKey, FunscriptOffset: &scriptOffset, - RedirectHeresphere: &redirectHeresphere, + ShowHeresphereButton: &showHeresphereButton, } } diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 8411f2ad684..931cb7e66f3 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -802,24 +802,6 @@ func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { } } -/* - * This http handler redirects HereSphere if enabled - */ -func heresphereHandler() func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c := config.GetInstance() - - if strings.Contains(r.UserAgent(), "HereSphere") && c.GetRedirectHeresphere() && (r.URL.Path == "/" || strings.HasPrefix(r.URL.Path, "/login")) { - http.Redirect(w, r, "/heresphere", http.StatusSeeOther) - return - } - - next.ServeHTTP(w, r) - }) - } -} - /* * This context function finds the applicable scene from the request and stores it. */ diff --git a/internal/api/server.go b/internal/api/server.go index 63a25768847..92409c17b29 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -60,7 +60,6 @@ func Start() error { r.Use(middleware.Heartbeat("/healthz")) r.Use(cors.AllowAll().Handler) - r.Use(heresphereHandler()) r.Use(authenticateHandler()) visitedPluginHandler := manager.GetInstance().SessionStore.VisitedPluginHandler() r.Use(visitedPluginHandler) diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index 9a760311fc8..64aa63935eb 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -99,8 +99,8 @@ const ( CreateImageClipsFromVideos = "create_image_clip_from_videos" createImageClipsFromVideosDefault = false - RedirectHeresphere = "redirect_heresphere" - redirectHeresphereDefault = false + ShowHeresphereButton = "show_scene_heresphere_button" + showHeresphereButtonDefault = false Host = "host" hostDefault = "0.0.0.0" @@ -875,8 +875,8 @@ func (i *Instance) IsCreateImageClipsFromVideos() bool { return i.getBool(CreateImageClipsFromVideos) } -func (i *Instance) GetRedirectHeresphere() bool { - return i.getBool(RedirectHeresphere) +func (i *Instance) GetShowHeresphereButton() bool { + return i.getBool(ShowHeresphereButton) } func (i *Instance) GetAPIKey() string { @@ -1529,7 +1529,7 @@ func (i *Instance) setDefaultValues(write bool) error { i.main.SetDefault(WriteImageThumbnails, writeImageThumbnailsDefault) i.main.SetDefault(CreateImageClipsFromVideos, createImageClipsFromVideosDefault) - i.main.SetDefault(RedirectHeresphere, redirectHeresphereDefault) + i.main.SetDefault(ShowHeresphereButton, showHeresphereButtonDefault) i.main.SetDefault(Database, defaultDatabaseFilePath) diff --git a/ui/v2.5/src/components/MainNavbar.tsx b/ui/v2.5/src/components/MainNavbar.tsx index 34389e0cf18..6f3a4f77c95 100644 --- a/ui/v2.5/src/components/MainNavbar.tsx +++ b/ui/v2.5/src/components/MainNavbar.tsx @@ -31,6 +31,7 @@ import { faTimes, faUser, faVideo, + faHelmetSafety, } from "@fortawesome/free-solid-svg-icons"; import { baseURL } from "src/core/createClient"; @@ -287,6 +288,21 @@ export const MainNavbar: React.FC = () => { + {configuration?.interface.showHeresphereButton && + + + + } { onChange={(v) => saveUI({ abbreviateCounters: v })} /> saveInterface({ redirectHeresphere: v })} + id="show-heresphere-button" + headingID="config.ui.show_heresphere_button.heading" + subHeadingID="config.ui.show_heresphere_button.description" + checked={iface.showHeresphereButton ?? undefined} + onChange={(v) => saveInterface({ showHeresphereButton: v })} /> diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index a6fbe37694d..09464d9d0ae 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -609,9 +609,9 @@ "language": { "heading": "Language" }, - "redirect_heresphere": { - "description": "Redirect Heresphere from the interface to its own API", - "heading": "Redirect Heresphere" + "show_heresphere_button": { + "description": "Show a button to open to the HereSphere API", + "heading": "Show HereSphere button" }, "max_loop_duration": { "description": "Maximum scene duration where scene player will loop the video - 0 to disable", From db2ee3ee8ef1d7b4421d002bc4dba9f5f3b6f721 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 5 Jun 2023 17:25:00 +0200 Subject: [PATCH 036/144] Make linter happy --- ui/v2.5/src/components/MainNavbar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/v2.5/src/components/MainNavbar.tsx b/ui/v2.5/src/components/MainNavbar.tsx index 6f3a4f77c95..2db743fb14c 100644 --- a/ui/v2.5/src/components/MainNavbar.tsx +++ b/ui/v2.5/src/components/MainNavbar.tsx @@ -288,7 +288,7 @@ export const MainNavbar: React.FC = () => { - {configuration?.interface.showHeresphereButton && + {configuration?.interface.showHeresphereButton && ( { - } + )} Date: Mon, 5 Jun 2023 17:34:29 +0200 Subject: [PATCH 037/144] Aling button --- ui/v2.5/src/components/MainNavbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/v2.5/src/components/MainNavbar.tsx b/ui/v2.5/src/components/MainNavbar.tsx index 2db743fb14c..4c5b9bd0e49 100644 --- a/ui/v2.5/src/components/MainNavbar.tsx +++ b/ui/v2.5/src/components/MainNavbar.tsx @@ -296,7 +296,7 @@ export const MainNavbar: React.FC = () => { onClick={handleDismiss} > - {configuration?.interface.showHeresphereButton && ( - - - - )} { checked={ui.abbreviateCounters ?? undefined} onChange={(v) => saveUI({ abbreviateCounters: v })} /> - saveInterface({ showHeresphereButton: v })} - /> diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 83be15f835f..6775e71ca66 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -639,11 +639,7 @@ "language": { "heading": "Language" }, - "show_heresphere_button": { - "description": "Show a button to open to the HereSphere API", - "heading": "Show HereSphere button" - }, - "max_loop_duration": { + "max_loop_duration": { "description": "Maximum scene duration where scene player will loop the video - 0 to disable", "heading": "Maximum loop duration" }, From 9ddff62ed6b59a98cfbcb935b9390498b612f5ad Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:03:04 +0200 Subject: [PATCH 086/144] No more heresphere button 2 --- graphql/schema/types/config.graphql | 2 -- internal/api/resolver_mutation_configure.go | 1 - internal/api/resolver_query_configuration.go | 3 --- internal/manager/config/config.go | 16 ---------------- ui/v2.5/src/components/MainNavbar.tsx | 1 - 5 files changed, 23 deletions(-) diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 3db4815a391..a92bcc168de 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -435,8 +435,6 @@ type ConfigInterfaceResult { noBrowser: Boolean "True if we should send desktop notifications" notificationsEnabled: Boolean - """True if we should automatically redirect Heresphere clients to the Heresphere endpoint""" - showHeresphereButton: Boolean "If true, video will autostart on load in the scene player" autostartVideo: Boolean "If true, video will autostart when loading from play random or play selected" diff --git a/internal/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index 58b16bd2b3e..f12b3aa0cec 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -404,7 +404,6 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI setBool(config.NoBrowser, input.NoBrowser) setBool(config.NotificationsEnabled, input.NotificationsEnabled) - setBool(config.ShowHeresphereButton, input.ShowHeresphereButton) setBool(config.ShowScrubber, input.ShowScrubber) diff --git a/internal/api/resolver_query_configuration.go b/internal/api/resolver_query_configuration.go index ab1fcbc2f15..7de9bda0da6 100644 --- a/internal/api/resolver_query_configuration.go +++ b/internal/api/resolver_query_configuration.go @@ -160,7 +160,6 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult { handyKey := config.GetHandyKey() scriptOffset := config.GetFunscriptOffset() useStashHostedFunscript := config.GetUseStashHostedFunscript() - showHeresphereButton := config.GetShowHeresphereButton() imageLightboxOptions := config.GetImageLightboxOptions() // FIXME - misnamed output field means we have redundant fields disableDropdownCreate := config.GetDisableDropdownCreate() @@ -195,8 +194,6 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult { HandyKey: &handyKey, FunscriptOffset: &scriptOffset, UseStashHostedFunscript: &useStashHostedFunscript, - - ShowHeresphereButton: &showHeresphereButton, } } diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index c9a5bb40362..13f88d8a61f 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -97,9 +97,6 @@ const ( CreateImageClipsFromVideos = "create_image_clip_from_videos" createImageClipsFromVideosDefault = false - ShowHeresphereButton = "show_scene_heresphere_button" - showHeresphereButtonDefault = false - Host = "host" hostDefault = "0.0.0.0" @@ -217,8 +214,6 @@ const ( DLNADefaultIPWhitelist = "dlna.default_whitelist" DLNAInterfaces = "dlna.interfaces" - HeresphereEnabled = "heresphere.default_enabled" - DLNAVideoSortOrder = "dlna.video_sort_order" dlnaVideoSortOrderDefault = "title" @@ -874,10 +869,6 @@ func (i *Instance) IsCreateImageClipsFromVideos() bool { return i.getBool(CreateImageClipsFromVideos) } -func (i *Instance) GetShowHeresphereButton() bool { - return i.getBool(ShowHeresphereButton) -} - func (i *Instance) GetAPIKey() string { return i.getString(ApiKey) } @@ -1428,11 +1419,6 @@ func (i *Instance) GetVideoSortOrder() string { return ret } -// GetHeresphereDefaultEnabled returns true if the HereSphere API is enabled by default. -func (i *Instance) GetHeresphereDefaultEnabled() bool { - return i.getBool(HeresphereEnabled) -} - // GetLogFile returns the filename of the file to output logs to. // An empty string means that file logging will be disabled. func (i *Instance) GetLogFile() string { @@ -1564,8 +1550,6 @@ func (i *Instance) setDefaultValues(write bool) error { i.main.SetDefault(WriteImageThumbnails, writeImageThumbnailsDefault) i.main.SetDefault(CreateImageClipsFromVideos, createImageClipsFromVideosDefault) - i.main.SetDefault(ShowHeresphereButton, showHeresphereButtonDefault) - i.main.SetDefault(Database, defaultDatabaseFilePath) i.main.SetDefault(dangerousAllowPublicWithoutAuth, dangerousAllowPublicWithoutAuthDefault) diff --git a/ui/v2.5/src/components/MainNavbar.tsx b/ui/v2.5/src/components/MainNavbar.tsx index 6ba7073c653..34389e0cf18 100644 --- a/ui/v2.5/src/components/MainNavbar.tsx +++ b/ui/v2.5/src/components/MainNavbar.tsx @@ -31,7 +31,6 @@ import { faTimes, faUser, faVideo, - faHelmetSafety, } from "@fortawesome/free-solid-svg-icons"; import { baseURL } from "src/core/createClient"; From 724c16493f9222b637f9da6121b062347a817596 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:23:06 +0200 Subject: [PATCH 087/144] HSP Settings in Services section --- graphql/documents/data/config.graphql | 12 ++++ graphql/documents/mutations/config.graphql | 6 ++ .../documents/mutations/heresphere.graphql | 18 ++++++ graphql/schema/schema.graphql | 14 ++++ graphql/schema/types/config.graphql | 31 +++++++++ graphql/schema/types/heresphere.graphql | 30 +++++++++ internal/api/resolver_mutation_configure.go | 29 +++++++++ internal/api/resolver_mutation_hsp.go | 29 +++++++++ internal/api/resolver_query_configuration.go | 14 ++++ internal/api/routes_heresphere.go | 37 +++++------ internal/manager/config/config.go | 47 +++++++++++--- .../Settings/SettingsServicesPanel.tsx | 56 ++++++++++++++++ ui/v2.5/src/components/Settings/context.tsx | 64 +++++++++++++++++++ ui/v2.5/src/core/StashService.ts | 9 +++ 14 files changed, 369 insertions(+), 27 deletions(-) create mode 100644 graphql/documents/mutations/heresphere.graphql create mode 100644 graphql/schema/types/heresphere.graphql create mode 100644 internal/api/resolver_mutation_hsp.go diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index 019f56a6e1c..e546f1a51f6 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -105,6 +105,15 @@ fragment ConfigDLNAData on ConfigDLNAResult { videoSortOrder } +fragment ConfigHSPData on ConfigHSPResult { + enabled + favoriteTagId + writeFavorites + writeRatings + writeTags + writeDeletes +} + fragment ConfigScrapingData on ConfigScrapingResult { scraperUserAgent scraperCertCheck @@ -205,6 +214,9 @@ fragment ConfigData on ConfigResult { dlna { ...ConfigDLNAData } + hsp { + ...ConfigHSPData + } scraping { ...ConfigScrapingData } diff --git a/graphql/documents/mutations/config.graphql b/graphql/documents/mutations/config.graphql index dfd53ed757b..05810cdc523 100644 --- a/graphql/documents/mutations/config.graphql +++ b/graphql/documents/mutations/config.graphql @@ -24,6 +24,12 @@ mutation ConfigureDLNA($input: ConfigDLNAInput!) { } } +mutation ConfigureHSP($input: ConfigHSPInput!) { + configureHSP(input: $input) { + ...ConfigHSPData + } +} + mutation ConfigureScraping($input: ConfigScrapingInput!) { configureScraping(input: $input) { ...ConfigScrapingData diff --git a/graphql/documents/mutations/heresphere.graphql b/graphql/documents/mutations/heresphere.graphql new file mode 100644 index 00000000000..8e1b81dedcb --- /dev/null +++ b/graphql/documents/mutations/heresphere.graphql @@ -0,0 +1,18 @@ +mutation EnableHSP($input: EnableHSPInput!) { + enableHSP(input: $input) +} +mutation SetHSPFavoriteTag($input: FavoriteTagHSPInput!) { + setHSPFavoriteTag(input: $input) +} +mutation SetHSPFavoriteWrite($input: HSPFavoriteWrite!) { + setHSPWriteFavorites(input: $input) +} +mutation SetHSPRatingWrite($input: HSPRatingWrite!) { + setHSPWriteRatings(input: $input) +} +mutation SetHSPTagWrite($input: HSPTagWrite!) { + setHSPWriteTags(input: $input) +} +mutation SetHSPDeleteWrite($input: HSPDeleteWrite!) { + setHSPWriteDeletes(input: $input) +} diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index 52f97adab31..d799fb01def 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -341,6 +341,7 @@ type Mutation { configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult! configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult! configureDLNA(input: ConfigDLNAInput!): ConfigDLNAResult! + configureHSP(input: ConfigHSPInput!): ConfigHSPResult! configureScraping(input: ConfigScrapingInput!): ConfigScrapingResult! configureDefaults( input: ConfigDefaultSettingsInput! @@ -434,6 +435,19 @@ type Mutation { addTempDLNAIP(input: AddTempDLNAIPInput!): Boolean! "Removes an IP address from the temporary DLNA whitelist" removeTempDLNAIP(input: RemoveTempDLNAIPInput!): Boolean! + + "Enables HSP API." + enableHSP(input: EnableHSPInput!): Boolean! + "Sets a tag as favorite in the HSP API." + setHSPFavoriteTag(input: FavoriteTagHSPInput!): Boolean! + "Enables writing favorites to the HSP API." + setHSPWriteFavorites(input: HSPFavoriteWrite!): Boolean! + "Enables writing ratings to the HSP API." + setHSPWriteRatings(input: HSPRatingWrite!): Boolean! + "Enables writing tags to the HSP API." + setHSPWriteTags(input: HSPTagWrite!): Boolean! + "Enables writing deletes to the HSP API." + setHSPWriteDeletes(input: HSPDeleteWrite!): Boolean! } type Subscription { diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index a92bcc168de..bd7c3f29266 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -502,6 +502,36 @@ type ConfigDLNAResult { videoSortOrder: String! } +input ConfigHSPInput { + "True if HSP Api should be enabled by default" + enabled: Boolean + "ID of the favorite tag" + favoriteTagId: Int + "True if writing favorites to HSP Api should be enabled" + writeFavorites: Boolean + "True if writing ratings to HSP Api should be enabled" + writeRatings: Boolean + "True if writing tags to HSP Api should be enabled" + writeTags: Boolean + "True if writing deletes to HSP Api should be enabled" + writeDeletes: Boolean +} + +type ConfigHSPResult { + "True if HSP Api should be enabled by default" + enabled: Boolean! + "ID of the favorite tag" + favoriteTagId: Int! + "True if writing favorites to HSP Api should be enabled" + writeFavorites: Boolean! + "True if writing ratings to HSP Api should be enabled" + writeRatings: Boolean! + "True if writing tags to HSP Api should be enabled" + writeTags: Boolean! + "True if writing deletes to HSP Api should be enabled" + writeDeletes: Boolean! +} + input ConfigScrapingInput { "Scraper user agent string" scraperUserAgent: String @@ -553,6 +583,7 @@ type ConfigResult { general: ConfigGeneralResult! interface: ConfigInterfaceResult! dlna: ConfigDLNAResult! + hsp: ConfigHSPResult! scraping: ConfigScrapingResult! defaults: ConfigDefaultSettingsResult! ui: Map! diff --git a/graphql/schema/types/heresphere.graphql b/graphql/schema/types/heresphere.graphql new file mode 100644 index 00000000000..f546d00fb50 --- /dev/null +++ b/graphql/schema/types/heresphere.graphql @@ -0,0 +1,30 @@ +input EnableHSPInput { + "If HSP Api should be enabled." + enabled: Boolean +} + +input FavoriteTagHSPInput { + "ID of the tag to be set as favorite." + tagId: ID! +} + + +input HSPFavoriteWrite { + "If writing favorites to HSP Api should be enabled." + enabled: Boolean +} + +input HSPRatingWrite { + "If writing ratings to HSP Api should be enabled." + enabled: Boolean +} + +input HSPTagWrite { + "If writing tags to HSP Api should be enabled." + enabled: Boolean +} + +input HSPDeleteWrite { + "If writing deletes to HSP Api should be enabled." + enabled: Boolean +} \ No newline at end of file diff --git a/internal/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index f12b3aa0cec..96456d412f0 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -532,6 +532,35 @@ func (r *mutationResolver) ConfigureDlna(ctx context.Context, input ConfigDLNAIn return makeConfigDLNAResult(), nil } +func (r *mutationResolver) ConfigureHsp(ctx context.Context, input ConfigHSPInput) (*ConfigHSPResult, error) { + c := config.GetInstance() + + if input.Enabled != nil { + c.Set(config.HSPDefaultEnabled, *input.Enabled) + } + if input.FavoriteTagID != nil { + c.Set(config.HSPFavoriteTag, *input.FavoriteTagID) + } + if input.WriteFavorites != nil { + c.Set(config.HSPWriteFavorites, *input.WriteFavorites) + } + if input.WriteRatings != nil { + c.Set(config.HSPWriteRating, *input.WriteRatings) + } + if input.WriteTags != nil { + c.Set(config.HSPWriteTags, *input.WriteTags) + } + if input.WriteDeletes != nil { + c.Set(config.HSPWriteDeletes, *input.WriteDeletes) + } + + if err := c.Write(); err != nil { + return makeConfigHSPResult(), err + } + + return makeConfigHSPResult(), nil +} + func (r *mutationResolver) ConfigureScraping(ctx context.Context, input ConfigScrapingInput) (*ConfigScrapingResult, error) { c := config.GetInstance() diff --git a/internal/api/resolver_mutation_hsp.go b/internal/api/resolver_mutation_hsp.go new file mode 100644 index 00000000000..e38fe31a385 --- /dev/null +++ b/internal/api/resolver_mutation_hsp.go @@ -0,0 +1,29 @@ +package api + +import ( + "context" +) + +func (r *mutationResolver) EnableHsp(ctx context.Context, input EnableHSPInput) (bool, error) { + return true, nil +} + +func (r *mutationResolver) SetHSPFavoriteTag(ctx context.Context, input FavoriteTagHSPInput) (bool, error) { + return true, nil +} + +func (r *mutationResolver) SetHSPWriteFavorites(ctx context.Context, input HSPFavoriteWrite) (bool, error) { + return true, nil +} + +func (r *mutationResolver) SetHSPWriteRatings(ctx context.Context, input HSPRatingWrite) (bool, error) { + return true, nil +} + +func (r *mutationResolver) SetHSPWriteTags(ctx context.Context, input HSPTagWrite) (bool, error) { + return true, nil +} + +func (r *mutationResolver) SetHSPWriteDeletes(ctx context.Context, input HSPDeleteWrite) (bool, error) { + return true, nil +} diff --git a/internal/api/resolver_query_configuration.go b/internal/api/resolver_query_configuration.go index 7de9bda0da6..1c80ff1ab8e 100644 --- a/internal/api/resolver_query_configuration.go +++ b/internal/api/resolver_query_configuration.go @@ -64,6 +64,7 @@ func makeConfigResult() *ConfigResult { General: makeConfigGeneralResult(), Interface: makeConfigInterfaceResult(), Dlna: makeConfigDLNAResult(), + Hsp: makeConfigHSPResult(), Scraping: makeConfigScrapingResult(), Defaults: makeConfigDefaultsResult(), UI: makeConfigUIResult(), @@ -209,6 +210,19 @@ func makeConfigDLNAResult() *ConfigDLNAResult { } } +func makeConfigHSPResult() *ConfigHSPResult { + config := config.GetInstance() + + return &ConfigHSPResult{ + Enabled: config.GetHSPDefaultEnabled(), + FavoriteTagID: config.GetHSPFavoriteTag(), + WriteFavorites: config.GetHSPWriteFavorites(), + WriteRatings: config.GetHSPWriteRatings(), + WriteTags: config.GetHSPWriteTags(), + WriteDeletes: config.GetHSPWriteDeletes(), + } +} + func makeConfigScrapingResult() *ConfigScrapingResult { config := config.GetInstance() diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 6740d34be5a..d3f8d24cb28 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -255,12 +255,10 @@ func getMinPlayPercent() (per int, err error) { } return } -func getFavoriteTag() (varTag string, err error) { - varTag = config.GetInstance().GetUIFavoriteTag() - if len(varTag) == 0 { - // err = fmt.Errorf("zero length favorite tag") - varTag = "Favorite" - // TODO: This is for development, remove forced assign +func getFavoriteTag() (varTag int, err error) { + varTag = config.GetInstance().GetHSPFavoriteTag() + if varTag < 0 { + err = fmt.Errorf("invalid favorite tag id") } return } @@ -273,7 +271,7 @@ func (rs heresphereRoutes) getVideoFavorite(r *http.Request, scene *models.Scene if err == nil { if favTag, err := getFavoriteTag(); err == nil { for _, tag := range tag_ids { - if tag.Name == favTag { + if tag.ID == favTag { return true } } @@ -351,6 +349,8 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) shouldUpdate := false fileDeleter := file.NewDeleter() + // Having this here is mostly paranoia + c := config.GetInstance() // Create update set ret := &scene.UpdateSet{ @@ -359,14 +359,14 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h ret.Partial = models.NewScenePartial() // Update rating - if user.Rating != nil { + if user.Rating != nil && c.GetHSPWriteRatings() { rating := models.Rating5To100F(*user.Rating) ret.Partial.Rating = models.NewOptionalInt(rating) shouldUpdate = true } // Delete primary file - if user.DeleteFile != nil && *user.DeleteFile { + if user.DeleteFile != nil && *user.DeleteFile && c.GetHSPWriteDeletes() { if err := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { fqb := rs.repository.File @@ -390,7 +390,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h } // Favorites tag - if favName, err := getFavoriteTag(); user.IsFavorite != nil && err == nil { + if favName, err := getFavoriteTag(); user.IsFavorite != nil && err == nil && c.GetHSPWriteFavorites() { favTag := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%v", favName)} if *user.IsFavorite { if user.Tags == nil { @@ -410,7 +410,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h } // Tags - if user.Tags != nil { + if user.Tags != nil && c.GetHSPWriteTags() { // Search input tags and add/create any new ones var tagIDs []int var perfIDs []int @@ -1062,6 +1062,7 @@ func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntr */ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) + c := config.GetInstance() // Update request if err := rs.HeresphereVideoDataUpdate(w, r); err != nil { @@ -1108,9 +1109,9 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Subtitles: rs.getVideoSubtitles(r, scene), Tags: rs.getVideoTags(r, scene), Media: []HeresphereVideoMedia{}, - WriteFavorite: true, - WriteRating: true, - WriteTags: true, + WriteFavorite: c.GetHSPWriteFavorites(), + WriteRating: c.GetHSPWriteRatings(), + WriteTags: c.GetHSPWriteTags(), WriteHSP: false, } @@ -1252,7 +1253,7 @@ func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { // Default video library := HeresphereIndexEntry{ Name: msg, - List: []string{fmt.Sprintf("%s/heresphere/doesnt-exist", GetBaseURL(r))}, + List: []string{}, } // Index idx := HeresphereIndex{ @@ -1317,11 +1318,11 @@ func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { */ func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // TODO: Enable and make the settings button - /*if !config.GetInstance().GetHeresphereDefaultEnabled() { + // Only if enabled + if !config.GetInstance().GetHSPDefaultEnabled() { writeNotAuthorized(w, r, "HereSphere API not enabled!") return - }*/ + } // Add JSON Header (using Add uses camel case and makes it invalid because "Json") w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index 13f88d8a61f..5ce42936728 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -217,6 +217,14 @@ const ( DLNAVideoSortOrder = "dlna.video_sort_order" dlnaVideoSortOrderDefault = "title" + // HSP options + HSPDefaultEnabled = "hsp.default_enabled" + HSPFavoriteTag = "hsp.favorite_tag" + HSPWriteFavorites = "hsp.write_favorites" + HSPWriteRating = "hsp.write_rating" + HSPWriteTags = "hsp.write_tags" + HSPWriteDeletes = "hsp.write_deletes" + // Logging options LogFile = "logFile" LogOut = "logOut" @@ -1128,15 +1136,6 @@ func (i *Instance) GetUIMinPlayPercent() int { return -1 } -func (i *Instance) GetUIFavoriteTag() string { - // TODO: Consolidate into heresphere settings - cfgMap := i.GetUIConfiguration() - if val, ok := cfgMap["favoriteTag"]; ok { - return val.(string) - } - - return "" -} func (i *Instance) SetUIConfiguration(v map[string]interface{}) { i.RLock() @@ -1419,6 +1418,36 @@ func (i *Instance) GetVideoSortOrder() string { return ret } +// GetHSPDefaultEnabled returns true if the HSP Api is enabled by default. +func (i *Instance) GetHSPDefaultEnabled() bool { + return i.getBool(HSPDefaultEnabled) +} + +// GetHSPFavoriteTag returns the favorites tag id +func (i *Instance) GetHSPFavoriteTag() int { + return i.getInt(HSPFavoriteTag) +} + +// GetHSPWriteFavorites returns if favorites should be written +func (i *Instance) GetHSPWriteFavorites() bool { + return i.getBool(HSPWriteFavorites) +} + +// GetHSPWriteRatings returns if ratings should be written +func (i *Instance) GetHSPWriteRatings() bool { + return i.getBool(HSPWriteRating) +} + +// GetHSPWriteTags returns if tags should be written +func (i *Instance) GetHSPWriteTags() bool { + return i.getBool(HSPWriteTags) +} + +// GetHSPWriteDeletes returns if deletions should happen +func (i *Instance) GetHSPWriteDeletes() bool { + return i.getBool(HSPWriteDeletes) +} + // GetLogFile returns the filename of the file to output logs to. // An empty string means that file logging will be disabled. func (i *Instance) GetLogFile() string { diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 38a1ccb7932..f128ff61b23 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -16,6 +16,7 @@ import { ModalComponent } from "../Shared/Modal"; import { SettingSection } from "./SettingSection"; import { BooleanSetting, + NumberSetting, StringListSetting, StringSetting, SelectSetting, @@ -37,9 +38,11 @@ export const SettingsServicesPanel: React.FC = () => { const { dlna, + hsp, loading: configLoading, error, saveDLNA, + saveHSP, } = React.useContext(SettingStateContext); // undefined to hide dialog, true for enable, false for disable @@ -475,6 +478,55 @@ export const SettingsServicesPanel: React.FC = () => { ); }; + const HSPSettingsForm: React.FC = () => { + return ( + <> + + saveHSP({ enabled: v })} + /> + + {/* TODO: This should really be a dropdown for different tags */} + saveHSP({ favoriteTagId: v })} + /> + + saveHSP({ writeFavorites: v })} + /> + saveHSP({ writeRatings: v })} + /> + saveHSP({ writeTags: v })} + /> + saveHSP({ writeDeletes: v })} + /> + + + + ); + }; + return (
{renderTempEnableDialog()} @@ -510,6 +562,10 @@ export const SettingsServicesPanel: React.FC = () => { + +

HereSphere API

+ +
); }; diff --git a/ui/v2.5/src/components/Settings/context.tsx b/ui/v2.5/src/components/Settings/context.tsx index cbb353a6bdb..84c8c223c27 100644 --- a/ui/v2.5/src/components/Settings/context.tsx +++ b/ui/v2.5/src/components/Settings/context.tsx @@ -11,6 +11,7 @@ import { useConfiguration, useConfigureDefaults, useConfigureDLNA, + useConfigureHSP, useConfigureGeneral, useConfigureInterface, useConfigureScraping, @@ -29,6 +30,7 @@ export interface ISettingsContextState { defaults: GQL.ConfigDefaultSettingsInput; scraping: GQL.ConfigScrapingInput; dlna: GQL.ConfigDlnaInput; + hsp: GQL.ConfigHspInput; ui: IUIConfig; // apikey isn't directly settable, so expose it here @@ -39,6 +41,7 @@ export interface ISettingsContextState { saveDefaults: (input: Partial) => void; saveScraping: (input: Partial) => void; saveDLNA: (input: Partial) => void; + saveHSP: (input: Partial) => void; saveUI: (input: Partial) => void; refetch: () => void; @@ -52,6 +55,7 @@ export const SettingStateContext = React.createContext({ defaults: {}, scraping: {}, dlna: {}, + hsp: {}, ui: {}, apiKey: "", saveGeneral: () => {}, @@ -59,6 +63,7 @@ export const SettingStateContext = React.createContext({ saveDefaults: () => {}, saveScraping: () => {}, saveDLNA: () => {}, + saveHSP: () => {}, saveUI: () => {}, refetch: () => {}, }); @@ -93,6 +98,10 @@ export const SettingsContext: React.FC = ({ children }) => { const [pendingDLNA, setPendingDLNA] = useState(); const [updateDLNAConfig] = useConfigureDLNA(); + const [hsp, setHSP] = useState({}); + const [pendingHSP, setPendingHSP] = useState(); + const [updateHSPConfig] = useConfigureHSP(); + const [ui, setUI] = useState({}); const [pendingUI, setPendingUI] = useState<{}>(); const [updateUIConfig] = useConfigureUI(); @@ -131,6 +140,7 @@ export const SettingsContext: React.FC = ({ children }) => { setDefaults({ ...withoutTypename(data.configuration.defaults) }); setScraping({ ...withoutTypename(data.configuration.scraping) }); setDLNA({ ...withoutTypename(data.configuration.dlna) }); + setHSP({ ...withoutTypename(data.configuration.hsp) }); setUI(data.configuration.ui); }, [data, error]); @@ -391,6 +401,57 @@ export const SettingsContext: React.FC = ({ children }) => { }); } + + // saves the configuration if no further changes are made after a half second + const saveHSPConfig = useDebounce( + async (input: GQL.ConfigHspInput) => { + try { + setUpdateSuccess(undefined); + await updateHSPConfig({ + variables: { + input, + }, + }); + + setPendingHSP(undefined); + onSuccess(); + } catch (e) { + setSaveError(e); + } + }, + [updateHSPConfig, onSuccess], + 500 + ); + + useEffect(() => { + if (!pendingHSP) { + return; + } + + saveHSPConfig(pendingHSP); + }, [pendingHSP, saveHSPConfig]); + + function saveHSP(input: Partial) { + if (!hsp) { + return; + } + + setHSP({ + ...hsp, + ...input, + }); + + setPendingHSP((current) => { + if (!current) { + return input; + } + return { + ...current, + ...input, + }; + }); + } + // saves the configuration if no further changes are made after a half second const saveUIConfig = useDebounce( async (input: IUIConfig) => { @@ -460,6 +521,7 @@ export const SettingsContext: React.FC = ({ children }) => { pendingDefaults || pendingScraping || pendingDLNA || + pendingHSP || pendingUI ) { return ( @@ -491,12 +553,14 @@ export const SettingsContext: React.FC = ({ children }) => { defaults, scraping, dlna, + hsp, ui, saveGeneral, saveInterface, saveDefaults, saveScraping, saveDLNA, + saveHSP, saveUI, refetch, }} diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index d9e89007a54..ea21840d111 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -2030,6 +2030,15 @@ export const useAddTempDLNAIP = () => GQL.useAddTempDlnaipMutation(); export const useRemoveTempDLNAIP = () => GQL.useRemoveTempDlnaipMutation(); +export const useConfigureHSP = () => + GQL.useConfigureHspMutation({ + update: updateConfiguration, + }); + +export const useEnableHSP = () => GQL.useEnableHspMutation(); + + + export const mutateReloadScrapers = () => client.mutate({ mutation: GQL.ReloadScrapersDocument, From bbdaa17c4addbc59b6efdd6abb50be98e1cca22b Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:06:42 +0200 Subject: [PATCH 088/144] Small ui fixes --- internal/api/routes_heresphere.go | 69 ++++++++++--------- .../Settings/SettingsServicesPanel.tsx | 9 ++- ui/v2.5/src/locales/en-GB.json | 14 ++++ 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index d3f8d24cb28..72abf10250d 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -250,18 +250,11 @@ func getVrTag() (varTag string, err error) { } func getMinPlayPercent() (per int, err error) { per = config.GetInstance().GetUIMinPlayPercent() - if per == -1 { + if per < 0 { err = fmt.Errorf("unset minimum play percent") } return } -func getFavoriteTag() (varTag int, err error) { - varTag = config.GetInstance().GetHSPFavoriteTag() - if varTag < 0 { - err = fmt.Errorf("invalid favorite tag id") - } - return -} /* * This auxiliary function searches for the "favorite" tag @@ -269,11 +262,10 @@ func getFavoriteTag() (varTag int, err error) { func (rs heresphereRoutes) getVideoFavorite(r *http.Request, scene *models.Scene) bool { tag_ids, err := rs.resolver.Scene().Tags(r.Context(), scene) if err == nil { - if favTag, err := getFavoriteTag(); err == nil { - for _, tag := range tag_ids { - if tag.ID == favTag { - return true - } + favTag := config.GetInstance().GetHSPFavoriteTag() + for _, tag := range tag_ids { + if tag.ID == favTag { + return true } } } @@ -299,9 +291,10 @@ func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.R // Add playDuration newTime := event.Time / 1000 - /*newDuration := scn.PlayDuration + // TODO: Datebug still exists + newDuration := 0.0 // scn.PlayDuration // TODO: Huge value bug - if newTime > scene.ResumeTime { + /*if newTime > scene.ResumeTime { newDuration += (newTime - scene.ResumeTime) }*/ @@ -332,11 +325,10 @@ func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.R } // Write - // TODO: Datebug still exists - /*if _, err := rs.resolver.Mutation().SceneSaveActivity(r.Context(), strconv.Itoa(scn.ID), &newTime, &newDuration); err != nil { + if _, err := rs.resolver.Mutation().SceneSaveActivity(r.Context(), strconv.Itoa(scn.ID), &newTime, &newDuration); err != nil { w.WriteHeader(http.StatusInternalServerError) return - }*/ + } w.WriteHeader(http.StatusOK) } @@ -390,23 +382,36 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h } // Favorites tag - if favName, err := getFavoriteTag(); user.IsFavorite != nil && err == nil && c.GetHSPWriteFavorites() { - favTag := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%v", favName)} - if *user.IsFavorite { - if user.Tags == nil { - user.Tags = &[]HeresphereVideoTag{favTag} - } else { - *user.Tags = append(*user.Tags, favTag) - } - } else if user.Tags != nil { - for i, tag := range *user.Tags { - if tag.Name == favTag.Name { - *user.Tags = append((*user.Tags)[:i], (*user.Tags)[i+1:]...) - break + if user.IsFavorite != nil && c.GetHSPWriteFavorites() { + if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + var err error + var favTag *models.Tag + + tagId := config.GetInstance().GetHSPFavoriteTag() + if favTag, err = rs.repository.Tag.Find(ctx, tagId); err == nil { + favTagVal := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%v", favTag.Name)} + if *user.IsFavorite { + if user.Tags == nil { + user.Tags = &[]HeresphereVideoTag{favTagVal} + } else { + *user.Tags = append(*user.Tags, favTagVal) + } + } else if user.Tags != nil { + for i, tag := range *user.Tags { + if tag.Name == favTagVal.Name { + *user.Tags = append((*user.Tags)[:i], (*user.Tags)[i+1:]...) + break + } + } } + shouldUpdate = true } + + return err + }); err != nil { + // TODO: Should i really stop here? + return err } - shouldUpdate = true } // Tags diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index f128ff61b23..10d9ba4dbe8 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -481,7 +481,7 @@ export const SettingsServicesPanel: React.FC = () => { const HSPSettingsForm: React.FC = () => { return ( <> - + { saveHSP({ favoriteTagId: v })} /> @@ -500,24 +501,28 @@ export const SettingsServicesPanel: React.FC = () => { saveHSP({ writeFavorites: v })} /> saveHSP({ writeRatings: v })} /> saveHSP({ writeTags: v })} /> saveHSP({ writeDeletes: v })} /> @@ -563,8 +568,6 @@ export const SettingsServicesPanel: React.FC = () => { -

HereSphere API

- ); diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 6775e71ca66..2b4efb9f2bc 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -248,6 +248,20 @@ "video_sort_order": "Default Video Sort Order", "video_sort_order_desc": "Order to sort videos by default." }, + "hsp": { + "title": "HereSphere API", + "enabled_by_default": "Enabled", + "favorites_tag": "Favorites tag", + "favorites_tag_desc": "What tag to add when HSP (HereSphere) sends a favorites request", + "write_favorites": "Write Favorites", + "write_favorites_desc": "Whether to enable HSP to write the favorite tag to videos", + "write_ratings": "Write Ratings", + "write_ratings_desc": "Wether to enable HSP to write ratings", + "write_tags": "Write Tags", + "write_tags_desc": "Wether to enable HSP to write tags", + "write_deletes": "Do deletes", + "write_deletes_desc": "Wether to allow file deletion via HSP Api" + }, "general": { "auth": { "api_key": "API Key", From 5ca7cf981477f0f8f0214b75200979f3b3b636b8 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:09:38 +0200 Subject: [PATCH 089/144] Formatting --- graphql/schema/types/heresphere.graphql | 3 +-- internal/api/routes_heresphere.go | 3 +-- ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx | 1 - ui/v2.5/src/components/Settings/context.tsx | 1 - ui/v2.5/src/core/StashService.ts | 2 -- 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/graphql/schema/types/heresphere.graphql b/graphql/schema/types/heresphere.graphql index f546d00fb50..fa03970a2e7 100644 --- a/graphql/schema/types/heresphere.graphql +++ b/graphql/schema/types/heresphere.graphql @@ -8,7 +8,6 @@ input FavoriteTagHSPInput { tagId: ID! } - input HSPFavoriteWrite { "If writing favorites to HSP Api should be enabled." enabled: Boolean @@ -27,4 +26,4 @@ input HSPTagWrite { input HSPDeleteWrite { "If writing deletes to HSP Api should be enabled." enabled: Boolean -} \ No newline at end of file +} diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 72abf10250d..2d8e6604317 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -292,8 +292,8 @@ func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.R // Add playDuration newTime := event.Time / 1000 // TODO: Datebug still exists + // Huge value bug newDuration := 0.0 // scn.PlayDuration - // TODO: Huge value bug /*if newTime > scene.ResumeTime { newDuration += (newTime - scene.ResumeTime) }*/ @@ -1242,7 +1242,6 @@ func addApiKey(urlS string) string { /* * This auxiliary writes a library with a fake name upon auth failure */ -// TODO: Does this even work in HereSphere? func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { // Banner banner := HeresphereBanner{ diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 10d9ba4dbe8..4b507dbfd14 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -526,7 +526,6 @@ export const SettingsServicesPanel: React.FC = () => { checked={hsp.writeDeletes ?? undefined} onChange={(v) => saveHSP({ writeDeletes: v })} /> -
); diff --git a/ui/v2.5/src/components/Settings/context.tsx b/ui/v2.5/src/components/Settings/context.tsx index 84c8c223c27..e316033e7eb 100644 --- a/ui/v2.5/src/components/Settings/context.tsx +++ b/ui/v2.5/src/components/Settings/context.tsx @@ -401,7 +401,6 @@ export const SettingsContext: React.FC = ({ children }) => { }); } - // saves the configuration if no further changes are made after a half second const saveHSPConfig = useDebounce( async (input: GQL.ConfigHspInput) => { diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index ea21840d111..e8ad665188c 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -2037,8 +2037,6 @@ export const useConfigureHSP = () => export const useEnableHSP = () => GQL.useEnableHspMutation(); - - export const mutateReloadScrapers = () => client.mutate({ mutation: GQL.ReloadScrapersDocument, From 80652057bc1c9d91b83005755520fa0da39baee9 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:15:27 +0200 Subject: [PATCH 090/144] Fix datebug --- internal/api/routes_heresphere.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 2d8e6604317..99dd14473b5 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -291,15 +291,14 @@ func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.R // Add playDuration newTime := event.Time / 1000 - // TODO: Datebug still exists - // Huge value bug - newDuration := 0.0 // scn.PlayDuration - /*if newTime > scene.ResumeTime { - newDuration += (newTime - scene.ResumeTime) - }*/ + newDuration := 0.0 + if newTime > scn.ResumeTime { + newDuration += (newTime - scn.ResumeTime) + } // Update PlayCount if per, err := getMinPlayPercent(); err == nil { + // Above min playback percent if file := scn.Files.Primary(); file != nil && newTime/file.Duration > float64(per)/100.0 { // Create update set ret := &scene.UpdateSet{ @@ -409,7 +408,6 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h return err }); err != nil { - // TODO: Should i really stop here? return err } } @@ -429,7 +427,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h } // If add tag - // TODO FUTURE: Switch to CutPrefix as it's nicer + // TODO FUTURE: Switch to CutPrefix as it's nicer (1.20+) if strings.HasPrefix(tagI.Name, "Tag:") { after := strings.TrimPrefix(tagI.Name, "Tag:") var err error From fa882708321a0054286644d258a105f45b76e4bf Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:19:57 +0200 Subject: [PATCH 091/144] Rating as tag --- internal/api/routes_heresphere.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 99dd14473b5..5297a942006 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -740,6 +740,15 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] processedTags = append(processedTags, genTag) } + if scene.Rating != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Rating:%d", + models.Rating100To5(*scene.Rating), + ), + } + processedTags = append(processedTags, genTag) + } + { genTag := HeresphereVideoTag{ Name: fmt.Sprintf("%s:%s", From 792a02eb872759d770233b77b9b6b60c14c0990b Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:22:35 +0200 Subject: [PATCH 092/144] Rename --- internal/api/routes_heresphere.go | 2 +- ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 5297a942006..4ea11cf033e 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -427,7 +427,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h } // If add tag - // TODO FUTURE: Switch to CutPrefix as it's nicer (1.20+) + // FUTURE IMPROVEMENT: Switch to CutPrefix as it's nicer (1.20+) if strings.HasPrefix(tagI.Name, "Tag:") { after := strings.TrimPrefix(tagI.Name, "Tag:") var err error diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 4b507dbfd14..7e715a4fae9 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -489,7 +489,7 @@ export const SettingsServicesPanel: React.FC = () => { onChange={(v) => saveHSP({ enabled: v })} /> - {/* TODO: This should really be a dropdown for different tags */} + {/* FUTURE IMPROVEMENT: This should really be a dropdown for different tags */} Date: Tue, 8 Aug 2023 17:25:11 +0200 Subject: [PATCH 093/144] Remove wrong comment --- internal/api/routes_heresphere.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 4ea11cf033e..35f71abb322 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -834,7 +834,6 @@ func (rs heresphereRoutes) getVideoSubtitles(r *http.Request, scene *models.Scen processedCaption := HeresphereVideoSubtitle{ Name: caption.Filename, Language: caption.LanguageCode, - // & causes chi router bug with \u0026 Url: addApiKey(fmt.Sprintf("%s?lang=%v&type=%v", urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetCaptionURL(), caption.LanguageCode, From 024a5ba37e196d7edc77f279eda94969706b9483 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:27:54 +0200 Subject: [PATCH 094/144] Proper set --- internal/api/routes_heresphere.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 35f71abb322..f8f912219ff 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -352,7 +352,8 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h // Update rating if user.Rating != nil && c.GetHSPWriteRatings() { rating := models.Rating5To100F(*user.Rating) - ret.Partial.Rating = models.NewOptionalInt(rating) + ret.Partial.Rating.Value = rating + ret.Partial.Rating.Set = true shouldUpdate = true } From 8c6031a80684e03d9e1f3c874c7464497fd20a0e Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:33:52 +0200 Subject: [PATCH 095/144] Comment --- internal/api/routes_heresphere.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index f8f912219ff..b62faa1d50d 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -390,6 +390,8 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h tagId := config.GetInstance().GetHSPFavoriteTag() if favTag, err = rs.repository.Tag.Find(ctx, tagId); err == nil { favTagVal := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%v", favTag.Name)} + + // Do the old switcheroo to figure out how to add the tag if *user.IsFavorite { if user.Tags == nil { user.Tags = &[]HeresphereVideoTag{favTagVal} @@ -404,6 +406,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h } } } + shouldUpdate = true } From 498b2dfe6cf40a3b39ac4a45db7967b6fc51d37a Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:44:09 +0200 Subject: [PATCH 096/144] Fixed favorites call --- internal/api/routes_heresphere.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index b62faa1d50d..d9f50810b83 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -398,7 +398,12 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h } else { *user.Tags = append(*user.Tags, favTagVal) } - } else if user.Tags != nil { + } else { + if user.Tags == nil { + sceneTags := rs.getVideoTags(r, scn) + user.Tags = &sceneTags + } + for i, tag := range *user.Tags { if tag.Name == favTagVal.Name { *user.Tags = append((*user.Tags)[:i], (*user.Tags)[i+1:]...) @@ -423,8 +428,6 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h var perfIDs []int for _, tagI := range *user.Tags { - fmt.Printf("Tag name: %v\n", tagI.Name) - // If missing if len(tagI.Name) == 0 { continue @@ -447,6 +450,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h if tagMod != nil { tagIDs = append(tagIDs, tagMod.ID) } + continue } // If add performer @@ -470,6 +474,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h if tagMod != nil { perfIDs = append(perfIDs, tagMod.ID) } + continue } // If add marker @@ -517,6 +522,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h } } } + continue } if strings.HasPrefix(tagI.Name, "Movie:") { @@ -535,6 +541,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h SceneIndex: &scn.ID, }) } + continue } if strings.HasPrefix(tagI.Name, "Studio:") { after := strings.TrimPrefix(tagI.Name, "Studio:") @@ -549,11 +556,13 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h ret.Partial.StudioID.Set = true ret.Partial.StudioID.Value = tagMod.ID } + continue } if strings.HasPrefix(tagI.Name, "Director:") { after := strings.TrimPrefix(tagI.Name, "Director:") ret.Partial.Director.Set = true ret.Partial.Director.Value = after + continue } // Custom From a8c3ebc4e20117b18851a4a6c38cdab920d0b642 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:47:38 +0200 Subject: [PATCH 097/144] Small comment --- internal/api/routes_heresphere.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index d9f50810b83..34ae12b8b85 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -435,6 +435,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h // If add tag // FUTURE IMPROVEMENT: Switch to CutPrefix as it's nicer (1.20+) + // FUTURE IMPROVEMENT: Consider batching searches if strings.HasPrefix(tagI.Name, "Tag:") { after := strings.TrimPrefix(tagI.Name, "Tag:") var err error From 156bdffa525892e52e2e95961a14b714bc2f349e Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 8 Aug 2023 22:33:28 +0200 Subject: [PATCH 098/144] Smol comment --- ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 7e715a4fae9..fff6cd9188a 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -482,6 +482,7 @@ export const SettingsServicesPanel: React.FC = () => { return ( <> + The VR Tag and Minimum Play Percent from the Interface settings are reused for the HSP (HereSphere) Api Date: Tue, 8 Aug 2023 22:34:08 +0200 Subject: [PATCH 099/144] Small interface update --- ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx | 2 +- ui/v2.5/src/locales/en-GB.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index fff6cd9188a..44ea66d6acf 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -482,7 +482,7 @@ export const SettingsServicesPanel: React.FC = () => { return ( <> - The VR Tag and Minimum Play Percent from the Interface settings are reused for the HSP (HereSphere) Api + The "VR Tag" and "Minimum Play Percent" from the Interface settings are reused for the HSP (HereSphere) Api Date: Tue, 8 Aug 2023 22:48:13 +0200 Subject: [PATCH 100/144] Proper subheading --- ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx | 3 +-- ui/v2.5/src/locales/en-GB.json | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 44ea66d6acf..153587fccda 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -481,8 +481,7 @@ export const SettingsServicesPanel: React.FC = () => { const HSPSettingsForm: React.FC = () => { return ( <> - - The "VR Tag" and "Minimum Play Percent" from the Interface settings are reused for the HSP (HereSphere) Api + Date: Tue, 8 Aug 2023 23:02:12 +0200 Subject: [PATCH 101/144] Prettier --- ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 153587fccda..4fd046d9a95 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -481,7 +481,10 @@ export const SettingsServicesPanel: React.FC = () => { const HSPSettingsForm: React.FC = () => { return ( <> - + Date: Tue, 8 Aug 2023 23:57:31 +0200 Subject: [PATCH 102/144] Add proper error check to interactive tag --- internal/api/routes_heresphere.go | 36 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 34ae12b8b85..43c77a94820 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -389,7 +389,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h tagId := config.GetInstance().GetHSPFavoriteTag() if favTag, err = rs.repository.Tag.Find(ctx, tagId); err == nil { - favTagVal := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%v", favTag.Name)} + favTagVal := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%s", favTag.Name)} // Do the old switcheroo to figure out how to add the tag if *user.IsFavorite { @@ -683,7 +683,7 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] } genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Marker:%v", tagName), + Name: fmt.Sprintf("Marker:%s", tagName), Start: mark.Seconds * 1000, End: (mark.Seconds + 60) * 1000, } @@ -694,7 +694,7 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] if gallery_ids, err := rs.resolver.Scene().Galleries(r.Context(), scene); err == nil { for _, gal := range gallery_ids { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Gallery:%v", gal.GetTitle()), + Name: fmt.Sprintf("Gallery:%s", gal.GetTitle()), } processedTags = append(processedTags, genTag) } @@ -703,7 +703,7 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] if tag_ids, err := rs.resolver.Scene().Tags(r.Context(), scene); err == nil { for _, tag := range tag_ids { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Tag:%v", tag.Name), + Name: fmt.Sprintf("Tag:%s", tag.Name), } processedTags = append(processedTags, genTag) } @@ -722,7 +722,7 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] for _, movie := range movie_ids { if movie.Movie != nil { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Movie:%v", movie.Movie.Name), + Name: fmt.Sprintf("Movie:%s", movie.Movie.Name), } processedTags = append(processedTags, genTag) } @@ -731,13 +731,12 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] if studio_id, err := rs.resolver.Scene().Studio(r.Context(), scene); err == nil && studio_id != nil { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Studio:%v", studio_id.Name), + Name: fmt.Sprintf("Studio:%s", studio_id.Name), } processedTags = append(processedTags, genTag) } - { - interactive, _ := rs.resolver.Scene().Interactive(r.Context(), scene) + if interactive, err := rs.resolver.Scene().Interactive(r.Context(), scene); err == nil { genTag := HeresphereVideoTag{ Name: fmt.Sprintf("%s:%s", string(HeresphereCustomTagInteractive), @@ -747,9 +746,18 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] processedTags = append(processedTags, genTag) } + if interactiveSpeed, err := rs.resolver.Scene().InteractiveSpeed(r.Context(), scene); err == nil && interactiveSpeed != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Funspeed:%d", + *interactiveSpeed, + ), + } + processedTags = append(processedTags, genTag) + } + if len(scene.Director) > 0 { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Director:%v", scene.Director), + Name: fmt.Sprintf("Director:%s", scene.Director), } processedTags = append(processedTags, genTag) } @@ -805,13 +813,13 @@ func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) [] { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%v:%v", string(HeresphereCustomTagPlayCount), scene.PlayCount), + Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagPlayCount), scene.PlayCount), } processedTags = append(processedTags, genTag) } { genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%v:%v", string(HeresphereCustomTagOCounter), scene.OCounter), + Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagOCounter), scene.OCounter), } processedTags = append(processedTags, genTag) } @@ -848,7 +856,7 @@ func (rs heresphereRoutes) getVideoSubtitles(r *http.Request, scene *models.Scen processedCaption := HeresphereVideoSubtitle{ Name: caption.Filename, Language: caption.LanguageCode, - Url: addApiKey(fmt.Sprintf("%s?lang=%v&type=%v", + Url: addApiKey(fmt.Sprintf("%s?lang=%s&type=%s", urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetCaptionURL(), caption.LanguageCode, caption.CaptionType, @@ -962,7 +970,7 @@ func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Reques // Create scene list sceneUrls := make([]string, len(scenes)) for idx, scene := range scenes { - sceneUrls[idx] = fmt.Sprintf("%s/heresphere/%v", + sceneUrls[idx] = fmt.Sprintf("%s/heresphere/%d", GetBaseURL(r), scene.ID, ) @@ -1126,7 +1134,7 @@ func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Re Fov: 180.0, Lens: HeresphereLensLinear, CameraIPD: 6.5, - EventServer: addApiKey(fmt.Sprintf("%s/heresphere/%v/event", + EventServer: addApiKey(fmt.Sprintf("%s/heresphere/%d/event", GetBaseURL(r), scene.ID, )), From 99ae36e249ddb711da7020f8a0a744bf72c9cfa8 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:42:40 +0200 Subject: [PATCH 103/144] Small translation update --- internal/api/routes_heresphere.go | 8 ++++---- ui/v2.5/src/locales/en-GB.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 43c77a94820..b0e4d67d3fc 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -1237,7 +1237,7 @@ func HeresphereHasValidToken(r *http.Request) bool { apiKey := r.Header.Get(HeresphereAuthHeader) // Check url query auth - if apiKey == "" { + if len(apiKey) == 0 { apiKey = r.URL.Query().Get(session.ApiKeyParameter) } @@ -1350,15 +1350,15 @@ func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { */ func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Add JSON Header (using Add uses camel case and makes it invalid because "Json") + w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} + // Only if enabled if !config.GetInstance().GetHSPDefaultEnabled() { writeNotAuthorized(w, r, "HereSphere API not enabled!") return } - // Add JSON Header (using Add uses camel case and makes it invalid because "Json") - w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} - // Read HTTP Body body, err := io.ReadAll(r.Body) if err != nil { diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 99f980cef83..698f1ae4b9f 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -250,7 +250,7 @@ }, "hsp": { "title": "HereSphere API", - "desc": "The "VR Tag" and "Minimum Play Percent" from the Interface settings panel are reused for the HSP (HereSphere) API", + "desc": "The 'VR Tag' and 'Minimum Play Percent' from the Interface settings panel are reused for the HSP (HereSphere) API", "enabled_by_default": "Enabled", "favorites_tag": "Favorites Tag ID", "favorites_tag_desc": "What tag to add when HSP sends a favorites request", From 1cc55d6d280f1abc97f927a27241796e2a89da87 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:08:01 +0200 Subject: [PATCH 104/144] Set FOV when tag found --- internal/api/routes_heresphere.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index b0e4d67d3fc..038da74da7e 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -1010,7 +1010,7 @@ func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntr if strings.HasSuffix(tagPre, "°") { deg := strings.TrimSuffix(tagPre, "°") if s, err := strconv.ParseFloat(deg, 64); err == nil { - processedScene.Fov = float64(s) + processedScene.Fov = s } } // Has VR tag @@ -1056,14 +1056,18 @@ func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntr // Projection settings if strings.Contains(path, "_EAC360") || strings.Contains(path, "_360EAC") { processedScene.Projection = HeresphereProjectionEquirectangularCubemap + processedScene.Fov = 360.0 } if strings.Contains(path, "_360") { processedScene.Projection = HeresphereProjectionEquirectangular360 + processedScene.Fov = 360.0 } if strings.Contains(path, "_F180") || strings.Contains(path, "_180F") || strings.Contains(path, "_VR180") { processedScene.Projection = HeresphereProjectionFisheye + processedScene.Fov = 180.0 } else if strings.Contains(path, "_180") { processedScene.Projection = HeresphereProjectionEquirectangular + processedScene.Fov = 180.0 } if strings.Contains(path, "_MKX200") { processedScene.Projection = HeresphereProjectionFisheye From bf71d52a2740f7201ca789e106e02944c3c4dad4 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 10 Aug 2023 12:53:18 +0200 Subject: [PATCH 105/144] Mistake in marker handling, might need a closer look --- internal/api/routes_heresphere.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go index 038da74da7e..b478285444d 100644 --- a/internal/api/routes_heresphere.go +++ b/internal/api/routes_heresphere.go @@ -484,12 +484,12 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h var tagId *string if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { var err error - var tagMods []*models.MarkerStringsResultType + var markerResult []*models.MarkerStringsResultType searchType := "count" // Search for marker - if tagMods, err = rs.repository.SceneMarker.GetMarkerStrings(ctx, &after, &searchType); err == nil && len(tagMods) > 0 { - tagId = &tagMods[0].ID + if markerResult, err = rs.repository.SceneMarker.GetMarkerStrings(ctx, &after, &searchType); err == nil && len(markerResult) > 0 { + tagId = &markerResult[0].ID // Search for tag if markers, err := rs.repository.SceneMarker.FindBySceneID(r.Context(), scn.ID); err == nil { @@ -512,7 +512,7 @@ func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *h return err }); err != nil { // Create marker - if tagId == nil { + if tagId != nil { newTag := SceneMarkerCreateInput{ Seconds: tagI.Start, SceneID: strconv.Itoa(scn.ID), From 04567a60878a3fbdd6620f2425d03907c135a996 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 14 Aug 2023 00:50:43 +0200 Subject: [PATCH 106/144] UI heresphere favorite tag is now a dropdown Its a little blinky, but i dont know react well enough to fix it, so i'll call it good --- .../Settings/SettingsServicesPanel.tsx | 46 ++++++++++++++++--- ui/v2.5/src/locales/en-GB.json | 2 +- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 4fd046d9a95..19b896eda38 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { Button, Form } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { @@ -7,7 +7,9 @@ import { useEnableDLNA, useAddTempDLNAIP, useRemoveTempDLNAIP, + queryFindTags, } from "src/core/StashService"; +import * as GQL from "src/core/generated-graphql"; import { useToast } from "src/hooks/Toast"; import { DurationInput } from "../Shared/DurationInput"; import { Icon } from "../Shared/Icon"; @@ -16,7 +18,6 @@ import { ModalComponent } from "../Shared/Modal"; import { SettingSection } from "./SettingSection"; import { BooleanSetting, - NumberSetting, StringListSetting, StringSetting, SelectSetting, @@ -31,6 +32,7 @@ import { faTimes, faUserClock, } from "@fortawesome/free-solid-svg-icons"; +import { ListFilterModel } from "src/models/list-filter/filter"; export const SettingsServicesPanel: React.FC = () => { const intl = useIntl(); @@ -479,6 +481,26 @@ export const SettingsServicesPanel: React.FC = () => { }; const HSPSettingsForm: React.FC = () => { + const [data, setData] = useState(null); + + useEffect(() => { + const fetchData = async () => { + const filter = new ListFilterModel(GQL.FilterMode.Tags); + filter.itemsPerPage = 1000000; + + const result = await queryFindTags(filter); + const cardCount = result.data?.findTags.count; + + if (!result.loading && !cardCount) { + console.log("error loading tags!"); + return null; + } + + setData(result.data); + } + fetchData() + }, []) + return ( <> { onChange={(v) => saveHSP({ enabled: v })} /> - {/* FUTURE IMPROVEMENT: This should really be a dropdown for different tags */} - saveHSP({ favoriteTagId: v })} - /> + onChange={(v) => + saveHSP({ favoriteTagId: parseInt(v) }) + } + value={ + hsp.favoriteTagId !== undefined && hsp.favoriteTagId !== null ? hsp.favoriteTagId.toString() : undefined + } + > + + {data != null && data.findTags.tags.map((q) => ( + + ))} + Date: Mon, 14 Aug 2023 00:54:20 +0200 Subject: [PATCH 107/144] UI Linter wants "key" in options --- .../Settings/SettingsServicesPanel.tsx | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 19b896eda38..455837a760f 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -497,9 +497,9 @@ export const SettingsServicesPanel: React.FC = () => { } setData(result.data); - } - fetchData() - }, []) + }; + fetchData(); + }, []); return ( <> @@ -518,19 +518,20 @@ export const SettingsServicesPanel: React.FC = () => { id="hsp-favorites-tag" headingID="config.hsp.favorites_tag" subHeadingID="config.hsp.favorites_tag_desc" - onChange={(v) => - saveHSP({ favoriteTagId: parseInt(v) }) - } + onChange={(v) => saveHSP({ favoriteTagId: parseInt(v) })} value={ - hsp.favoriteTagId !== undefined && hsp.favoriteTagId !== null ? hsp.favoriteTagId.toString() : undefined + hsp.favoriteTagId !== undefined && hsp.favoriteTagId !== null + ? hsp.favoriteTagId.toString() + : undefined } > - {data != null && data.findTags.tags.map((q) => ( - - ))} + {data != null && + data.findTags.tags.map((q) => ( + + ))} Date: Mon, 14 Aug 2023 14:17:26 +0200 Subject: [PATCH 108/144] UI Do tag select in a little bit nice way --- .../Settings/SettingsServicesPanel.tsx | 62 +++++++------------ 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 455837a760f..4940ddf6d52 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -32,7 +32,7 @@ import { faTimes, faUserClock, } from "@fortawesome/free-solid-svg-icons"; -import { ListFilterModel } from "src/models/list-filter/filter"; +import { TagSelect } from "../Shared/Select"; export const SettingsServicesPanel: React.FC = () => { const intl = useIntl(); @@ -481,26 +481,6 @@ export const SettingsServicesPanel: React.FC = () => { }; const HSPSettingsForm: React.FC = () => { - const [data, setData] = useState(null); - - useEffect(() => { - const fetchData = async () => { - const filter = new ListFilterModel(GQL.FilterMode.Tags); - filter.itemsPerPage = 1000000; - - const result = await queryFindTags(filter); - const cardCount = result.data?.findTags.count; - - if (!result.loading && !cardCount) { - console.log("error loading tags!"); - return null; - } - - setData(result.data); - }; - fetchData(); - }, []); - return ( <> { onChange={(v) => saveHSP({ enabled: v })} /> - saveHSP({ favoriteTagId: parseInt(v) })} - value={ - hsp.favoriteTagId !== undefined && hsp.favoriteTagId !== null - ? hsp.favoriteTagId.toString() - : undefined - } - > - - {data != null && - data.findTags.tags.map((q) => ( - - ))} - +
+
+

{intl.formatMessage({ id: "config.hsp.favorites_tag" })}

+
+ {intl.formatMessage({ id: "config.hsp.favorites_tag_desc" })} +
+
+
+ + saveHSP({ favoriteTagId: parseInt(items[0].id) }) + } + ids={ + hsp.favoriteTagId !== undefined && hsp.favoriteTagId !== null + ? [hsp.favoriteTagId.toString()] + : [] + } + hoverPlacement="right" + /> +
+
Date: Mon, 14 Aug 2023 14:44:20 +0200 Subject: [PATCH 109/144] UI Bring tag select into compliance with the rest of the UI --- ui/v2.5/src/components/Settings/Inputs.tsx | 4 +- .../Settings/SettingsServicesPanel.tsx | 44 +++++++++---------- ui/v2.5/src/components/Settings/styles.scss | 5 +++ 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/ui/v2.5/src/components/Settings/Inputs.tsx b/ui/v2.5/src/components/Settings/Inputs.tsx index 9ecf1d54c3d..bf5786ad781 100644 --- a/ui/v2.5/src/components/Settings/Inputs.tsx +++ b/ui/v2.5/src/components/Settings/Inputs.tsx @@ -8,6 +8,7 @@ import { StringListInput } from "../Shared/StringListInput"; interface ISetting { id?: string; className?: string; + subClassName?: string; heading?: React.ReactNode; headingID?: string; subHeadingID?: string; @@ -20,6 +21,7 @@ interface ISetting { export const Setting: React.FC> = ({ id, className, + subClassName, heading, headingID, subHeadingID, @@ -64,7 +66,7 @@ export const Setting: React.FC> = ({

{renderHeading()}

{renderSubHeading()} -
{children}
+
{children}
); }; diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 4940ddf6d52..6cf3807f1d5 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { Button, Form } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { @@ -7,9 +7,7 @@ import { useEnableDLNA, useAddTempDLNAIP, useRemoveTempDLNAIP, - queryFindTags, } from "src/core/StashService"; -import * as GQL from "src/core/generated-graphql"; import { useToast } from "src/hooks/Toast"; import { DurationInput } from "../Shared/DurationInput"; import { Icon } from "../Shared/Icon"; @@ -21,6 +19,7 @@ import { StringListSetting, StringSetting, SelectSetting, + Setting, } from "./Inputs"; import { SettingStateContext } from "./context"; import { @@ -494,27 +493,24 @@ export const SettingsServicesPanel: React.FC = () => { onChange={(v) => saveHSP({ enabled: v })} /> -
-
-

{intl.formatMessage({ id: "config.hsp.favorites_tag" })}

-
- {intl.formatMessage({ id: "config.hsp.favorites_tag_desc" })} -
-
-
- - saveHSP({ favoriteTagId: parseInt(items[0].id) }) - } - ids={ - hsp.favoriteTagId !== undefined && hsp.favoriteTagId !== null - ? [hsp.favoriteTagId.toString()] - : [] - } - hoverPlacement="right" - /> -
-
+ + + saveHSP({ favoriteTagId: parseInt(items[0].id) }) + } + ids={ + hsp.favoriteTagId !== undefined && hsp.favoriteTagId !== null + ? [hsp.favoriteTagId.toString()] + : [] + } + hoverPlacement="right" + /> + Date: Mon, 14 Aug 2023 15:06:37 +0200 Subject: [PATCH 110/144] UI Linter happy --- ui/v2.5/src/components/Settings/Inputs.tsx | 6 +++--- ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx | 2 +- ui/v2.5/src/components/Settings/styles.scss | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/v2.5/src/components/Settings/Inputs.tsx b/ui/v2.5/src/components/Settings/Inputs.tsx index bf5786ad781..229ca43036c 100644 --- a/ui/v2.5/src/components/Settings/Inputs.tsx +++ b/ui/v2.5/src/components/Settings/Inputs.tsx @@ -8,7 +8,7 @@ import { StringListInput } from "../Shared/StringListInput"; interface ISetting { id?: string; className?: string; - subClassName?: string; + subElementId?: string; heading?: React.ReactNode; headingID?: string; subHeadingID?: string; @@ -21,7 +21,7 @@ interface ISetting { export const Setting: React.FC> = ({ id, className, - subClassName, + subElementId, heading, headingID, subHeadingID, @@ -66,7 +66,7 @@ export const Setting: React.FC> = ({

{renderHeading()}

{renderSubHeading()} -
{children}
+
{children}
); }; diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index 6cf3807f1d5..fc5be55c876 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -495,7 +495,7 @@ export const SettingsServicesPanel: React.FC = () => { diff --git a/ui/v2.5/src/components/Settings/styles.scss b/ui/v2.5/src/components/Settings/styles.scss index a69f4e26aa8..432048acf65 100644 --- a/ui/v2.5/src/components/Settings/styles.scss +++ b/ui/v2.5/src/components/Settings/styles.scss @@ -349,9 +349,9 @@ } } -.hsp-select { - min-width: 175px !important; - text-align: left !important; +#hsp-select { + min-width: 175px; + text-align: left; } .task-group { From 9b36deda64d2664adb71b33089042b7cd8e6d66a Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:52:38 +0200 Subject: [PATCH 111/144] Remove unused graphql --- .../documents/mutations/heresphere.graphql | 18 ------------ graphql/schema/schema.graphql | 13 --------- graphql/schema/types/heresphere.graphql | 29 ------------------- internal/api/resolver_mutation_hsp.go | 29 ------------------- 4 files changed, 89 deletions(-) delete mode 100644 graphql/documents/mutations/heresphere.graphql delete mode 100644 graphql/schema/types/heresphere.graphql delete mode 100644 internal/api/resolver_mutation_hsp.go diff --git a/graphql/documents/mutations/heresphere.graphql b/graphql/documents/mutations/heresphere.graphql deleted file mode 100644 index 8e1b81dedcb..00000000000 --- a/graphql/documents/mutations/heresphere.graphql +++ /dev/null @@ -1,18 +0,0 @@ -mutation EnableHSP($input: EnableHSPInput!) { - enableHSP(input: $input) -} -mutation SetHSPFavoriteTag($input: FavoriteTagHSPInput!) { - setHSPFavoriteTag(input: $input) -} -mutation SetHSPFavoriteWrite($input: HSPFavoriteWrite!) { - setHSPWriteFavorites(input: $input) -} -mutation SetHSPRatingWrite($input: HSPRatingWrite!) { - setHSPWriteRatings(input: $input) -} -mutation SetHSPTagWrite($input: HSPTagWrite!) { - setHSPWriteTags(input: $input) -} -mutation SetHSPDeleteWrite($input: HSPDeleteWrite!) { - setHSPWriteDeletes(input: $input) -} diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index d799fb01def..59de7326594 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -435,19 +435,6 @@ type Mutation { addTempDLNAIP(input: AddTempDLNAIPInput!): Boolean! "Removes an IP address from the temporary DLNA whitelist" removeTempDLNAIP(input: RemoveTempDLNAIPInput!): Boolean! - - "Enables HSP API." - enableHSP(input: EnableHSPInput!): Boolean! - "Sets a tag as favorite in the HSP API." - setHSPFavoriteTag(input: FavoriteTagHSPInput!): Boolean! - "Enables writing favorites to the HSP API." - setHSPWriteFavorites(input: HSPFavoriteWrite!): Boolean! - "Enables writing ratings to the HSP API." - setHSPWriteRatings(input: HSPRatingWrite!): Boolean! - "Enables writing tags to the HSP API." - setHSPWriteTags(input: HSPTagWrite!): Boolean! - "Enables writing deletes to the HSP API." - setHSPWriteDeletes(input: HSPDeleteWrite!): Boolean! } type Subscription { diff --git a/graphql/schema/types/heresphere.graphql b/graphql/schema/types/heresphere.graphql deleted file mode 100644 index fa03970a2e7..00000000000 --- a/graphql/schema/types/heresphere.graphql +++ /dev/null @@ -1,29 +0,0 @@ -input EnableHSPInput { - "If HSP Api should be enabled." - enabled: Boolean -} - -input FavoriteTagHSPInput { - "ID of the tag to be set as favorite." - tagId: ID! -} - -input HSPFavoriteWrite { - "If writing favorites to HSP Api should be enabled." - enabled: Boolean -} - -input HSPRatingWrite { - "If writing ratings to HSP Api should be enabled." - enabled: Boolean -} - -input HSPTagWrite { - "If writing tags to HSP Api should be enabled." - enabled: Boolean -} - -input HSPDeleteWrite { - "If writing deletes to HSP Api should be enabled." - enabled: Boolean -} diff --git a/internal/api/resolver_mutation_hsp.go b/internal/api/resolver_mutation_hsp.go deleted file mode 100644 index e38fe31a385..00000000000 --- a/internal/api/resolver_mutation_hsp.go +++ /dev/null @@ -1,29 +0,0 @@ -package api - -import ( - "context" -) - -func (r *mutationResolver) EnableHsp(ctx context.Context, input EnableHSPInput) (bool, error) { - return true, nil -} - -func (r *mutationResolver) SetHSPFavoriteTag(ctx context.Context, input FavoriteTagHSPInput) (bool, error) { - return true, nil -} - -func (r *mutationResolver) SetHSPWriteFavorites(ctx context.Context, input HSPFavoriteWrite) (bool, error) { - return true, nil -} - -func (r *mutationResolver) SetHSPWriteRatings(ctx context.Context, input HSPRatingWrite) (bool, error) { - return true, nil -} - -func (r *mutationResolver) SetHSPWriteTags(ctx context.Context, input HSPTagWrite) (bool, error) { - return true, nil -} - -func (r *mutationResolver) SetHSPWriteDeletes(ctx context.Context, input HSPDeleteWrite) (bool, error) { - return true, nil -} From 47a7eaa4359e382413ae360e4a64674c9efeeba3 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 30 Aug 2023 23:04:32 +0200 Subject: [PATCH 112/144] Initial refactor --- internal/api/authentication.go | 2 +- internal/api/context_keys.go | 2 - internal/api/resolver_model_scene.go | 8 +- internal/api/routes_heresphere.go | 1402 -------------------------- internal/api/server.go | 40 +- internal/api/session.go | 8 +- internal/api/types.go | 19 - internal/heresphere/auth.go | 99 ++ internal/heresphere/favorite.go | 80 ++ internal/heresphere/interfaces.go | 10 + internal/heresphere/keys.go | 8 + internal/heresphere/media.go | 162 +++ internal/heresphere/projections.go | 116 +++ internal/heresphere/routes.go | 456 +++++++++ internal/heresphere/schema.go | 164 +++ internal/heresphere/tags.go | 429 ++++++++ internal/heresphere/update.go | 70 ++ internal/heresphere/utils.go | 30 + internal/manager/http.go | 30 + internal/manager/json_utils.go | 19 + ui/v2.5/src/core/StashService.ts | 2 - 21 files changed, 1691 insertions(+), 1465 deletions(-) delete mode 100644 internal/api/routes_heresphere.go create mode 100644 internal/heresphere/auth.go create mode 100644 internal/heresphere/favorite.go create mode 100644 internal/heresphere/interfaces.go create mode 100644 internal/heresphere/keys.go create mode 100644 internal/heresphere/media.go create mode 100644 internal/heresphere/projections.go create mode 100644 internal/heresphere/routes.go create mode 100644 internal/heresphere/schema.go create mode 100644 internal/heresphere/tags.go create mode 100644 internal/heresphere/update.go create mode 100644 internal/heresphere/utils.go create mode 100644 internal/manager/http.go diff --git a/internal/api/authentication.go b/internal/api/authentication.go index 2295029b1c8..854bbb1d512 100644 --- a/internal/api/authentication.go +++ b/internal/api/authentication.go @@ -84,7 +84,7 @@ func authenticateHandler() func(http.Handler) http.Handler { return } - prefix := getProxyPrefix(r) + prefix := manager.GetProxyPrefix(r) // otherwise redirect to the login page returnURL := url.URL{ diff --git a/internal/api/context_keys.go b/internal/api/context_keys.go index 59e54bc6704..8731f75c301 100644 --- a/internal/api/context_keys.go +++ b/internal/api/context_keys.go @@ -13,6 +13,4 @@ const ( tagKey downloadKey imageKey - heresphereKey - heresphereUserKey ) diff --git a/internal/api/resolver_model_scene.go b/internal/api/resolver_model_scene.go index 9d5b41725ce..41df1232b08 100644 --- a/internal/api/resolver_model_scene.go +++ b/internal/api/resolver_model_scene.go @@ -23,12 +23,12 @@ func convertVideoFile(f *file.VideoFile) *VideoFile { ModTime: f.ModTime, Format: f.Format, Size: f.Size, - Duration: handleFloat64Value(f.Duration), + Duration: manager.HandleFloat64Value(f.Duration), VideoCodec: f.VideoCodec, AudioCodec: f.AudioCodec, Width: f.Width, Height: f.Height, - FrameRate: handleFloat64Value(f.FrameRate), + FrameRate: manager.HandleFloat64Value(f.FrameRate), BitRate: int(f.BitRate), CreatedAt: f.CreatedAt, UpdatedAt: f.UpdatedAt, @@ -122,12 +122,12 @@ func (r *sceneResolver) File(ctx context.Context, obj *models.Scene) (*models.Sc return &models.SceneFileType{ Size: &size, - Duration: handleFloat64(f.Duration), + Duration: manager.HandleFloat64(f.Duration), VideoCodec: &f.VideoCodec, AudioCodec: &f.AudioCodec, Width: &f.Width, Height: &f.Height, - Framerate: handleFloat64(f.FrameRate), + Framerate: manager.HandleFloat64(f.FrameRate), Bitrate: &bitrate, }, nil } diff --git a/internal/api/routes_heresphere.go b/internal/api/routes_heresphere.go deleted file mode 100644 index b478285444d..00000000000 --- a/internal/api/routes_heresphere.go +++ /dev/null @@ -1,1402 +0,0 @@ -package api - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "strings" - - "github.com/go-chi/chi" - "github.com/stashapp/stash/internal/api/loaders" - "github.com/stashapp/stash/internal/api/urlbuilders" - "github.com/stashapp/stash/internal/manager" - "github.com/stashapp/stash/internal/manager/config" - "github.com/stashapp/stash/pkg/file" - "github.com/stashapp/stash/pkg/logger" - "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/session" - "github.com/stashapp/stash/pkg/txn" -) - -// Based on HereSphere_JSON_API_Version_1.txt - -const HeresphereJsonVersion = 1 - -const ( - HeresphereGuest = 0 - HeresphereMember = 1 - HeresphereBadLogin = -1 -) - -type HeresphereProjection string - -const ( - HeresphereProjectionEquirectangular HeresphereProjection = "equirectangular" - HeresphereProjectionPerspective HeresphereProjection = "perspective" - HeresphereProjectionEquirectangular360 HeresphereProjection = "equirectangular360" - HeresphereProjectionFisheye HeresphereProjection = "fisheye" - HeresphereProjectionCubemap HeresphereProjection = "cubemap" - HeresphereProjectionEquirectangularCubemap HeresphereProjection = "equiangularCubemap" -) - -type HeresphereStereo string - -const ( - HeresphereStereoMono HeresphereStereo = "mono" - HeresphereStereoSbs HeresphereStereo = "sbs" - HeresphereStereoTB HeresphereStereo = "tb" -) - -type HeresphereLens string - -const ( - HeresphereLensLinear HeresphereLens = "Linear" - HeresphereLensMKX220 HeresphereLens = "MKX220" - HeresphereLensMKX200 HeresphereLens = "MKX200" - HeresphereLensVRCA220 HeresphereLens = "VRCA220" -) - -type HeresphereEventType int - -const ( - HeresphereEventOpen HeresphereEventType = 0 - HeresphereEventPlay HeresphereEventType = 1 - HeresphereEventPause HeresphereEventType = 2 - HeresphereEventClose HeresphereEventType = 3 -) - -type HeresphereCustomTag string - -const ( - HeresphereCustomTagInteractive HeresphereCustomTag = "Interactive" - - HeresphereCustomTagPlayCount HeresphereCustomTag = "PlayCount" - HeresphereCustomTagWatched HeresphereCustomTag = "Watched" - - HeresphereCustomTagOrganized HeresphereCustomTag = "Organized" - - HeresphereCustomTagOCounter HeresphereCustomTag = "OCounter" - HeresphereCustomTagOrgasmed HeresphereCustomTag = "Orgasmed" - - HeresphereCustomTagRated HeresphereCustomTag = "Rated" -) - -const HeresphereAuthHeader = "auth-token" - -type HeresphereAuthResp struct { - AuthToken string `json:"auth-token"` - Access int `json:"access"` -} - -type HeresphereBanner struct { - Image string `json:"image"` - Link string `json:"link"` -} -type HeresphereIndexEntry struct { - Name string `json:"name"` - List []string `json:"list"` -} -type HeresphereIndex struct { - Access int `json:"access"` - Banner HeresphereBanner `json:"banner"` - Library []HeresphereIndexEntry `json:"library"` -} -type HeresphereVideoScript struct { - Name string `json:"name"` - Url string `json:"url"` - Rating float64 `json:"rating,omitempty"` -} -type HeresphereVideoSubtitle struct { - Name string `json:"name"` - Language string `json:"language"` - Url string `json:"url"` -} -type HeresphereVideoTag struct { - Name string `json:"name"` - Start float64 `json:"start,omitempty"` - End float64 `json:"end,omitempty"` - Track int `json:"track,omitempty"` - Rating float64 `json:"rating,omitempty"` -} -type HeresphereVideoMediaSource struct { - Resolution int `json:"resolution"` - Height int `json:"height"` - Width int `json:"width"` - // In bytes - Size int64 `json:"size"` - Url string `json:"url"` -} -type HeresphereVideoMedia struct { - // Media type (h265 etc.) - Name string `json:"name"` - Sources []HeresphereVideoMediaSource `json:"sources"` -} -type HeresphereVideoEntry struct { - Access int `json:"access"` - Title string `json:"title"` - Description string `json:"description"` - ThumbnailImage string `json:"thumbnailImage"` - ThumbnailVideo string `json:"thumbnailVideo,omitempty"` - DateReleased string `json:"dateReleased,omitempty"` - DateAdded string `json:"dateAdded,omitempty"` - Duration float64 `json:"duration,omitempty"` - Rating float64 `json:"rating,omitempty"` - Favorites int `json:"favorites"` - Comments int `json:"comments"` - IsFavorite bool `json:"isFavorite"` - Projection HeresphereProjection `json:"projection"` - Stereo HeresphereStereo `json:"stereo"` - IsEyeSwapped bool `json:"isEyeSwapped"` - Fov float64 `json:"fov,omitempty"` - Lens HeresphereLens `json:"lens"` - CameraIPD float64 `json:"cameraIPD"` - Hsp string `json:"hsp,omitempty"` - EventServer string `json:"eventServer,omitempty"` - Scripts []HeresphereVideoScript `json:"scripts,omitempty"` - Subtitles []HeresphereVideoSubtitle `json:"subtitles,omitempty"` - Tags []HeresphereVideoTag `json:"tags,omitempty"` - Media []HeresphereVideoMedia `json:"media,omitempty"` - WriteFavorite bool `json:"writeFavorite"` - WriteRating bool `json:"writeRating"` - WriteTags bool `json:"writeTags"` - WriteHSP bool `json:"writeHSP"` -} -type HeresphereVideoEntryShort struct { - Link string `json:"link"` - Title string `json:"title"` - DateReleased string `json:"dateReleased,omitempty"` - DateAdded string `json:"dateAdded,omitempty"` - Duration float64 `json:"duration,omitempty"` - Rating float64 `json:"rating,omitempty"` - Favorites int `json:"favorites"` - Comments int `json:"comments"` - IsFavorite bool `json:"isFavorite"` - Tags []HeresphereVideoTag `json:"tags"` -} -type HeresphereScanIndex struct { - ScanData []HeresphereVideoEntryShort `json:"scanData"` -} -type HeresphereAuthReq struct { - Username string `json:"username"` - Password string `json:"password"` - NeedsMediaSource *bool `json:"needsMediaSource,omitempty"` - IsFavorite *bool `json:"isFavorite,omitempty"` - Rating *float64 `json:"rating,omitempty"` - Tags *[]HeresphereVideoTag `json:"tags,omitempty"` - HspBase64 *string `json:"hsp,omitempty"` - DeleteFile *bool `json:"deleteFile,omitempty"` -} -type HeresphereVideoEvent struct { - Username string `json:"username"` - Id string `json:"id"` - Title string `json:"title"` - Event HeresphereEventType `json:"event"` - Time float64 `json:"time"` - Speed float64 `json:"speed"` - Utc float64 `json:"utc"` - ConnectionKey string `json:"connectionKey"` -} - -type heresphereRoutes struct { - txnManager txn.Manager - sceneFinder SceneFinder - fileFinder file.Finder - captionFinder CaptionFinder - repository manager.Repository - resolver ResolverRoot -} - -/* - * This function provides the possible routes for this api. - */ -func (rs heresphereRoutes) Routes() chi.Router { - r := chi.NewRouter() - - r.Route("/", func(r chi.Router) { - r.Use(rs.HeresphereCtx) - - r.Post("/", rs.HeresphereIndex) - r.Get("/", rs.HeresphereIndex) - r.Head("/", rs.HeresphereIndex) - - r.Post("/auth", rs.HeresphereLoginToken) - r.Route("/{sceneId}", func(r chi.Router) { - r.Use(rs.HeresphereSceneCtx) - - r.Post("/", rs.HeresphereVideoData) - r.Get("/", rs.HeresphereVideoData) - - r.Post("/event", rs.HeresphereVideoEvent) - }) - }) - - return r -} - -func getVrTag() (varTag string, err error) { - // Find setting - varTag = config.GetInstance().GetUIVRTag() - if len(varTag) == 0 { - err = fmt.Errorf("zero length vr tag") - } - return -} -func getMinPlayPercent() (per int, err error) { - per = config.GetInstance().GetUIMinPlayPercent() - if per < 0 { - err = fmt.Errorf("unset minimum play percent") - } - return -} - -/* - * This auxiliary function searches for the "favorite" tag - */ -func (rs heresphereRoutes) getVideoFavorite(r *http.Request, scene *models.Scene) bool { - tag_ids, err := rs.resolver.Scene().Tags(r.Context(), scene) - if err == nil { - favTag := config.GetInstance().GetHSPFavoriteTag() - for _, tag := range tag_ids { - if tag.ID == favTag { - return true - } - } - } - - return false -} - -/* - * This is a video playback event - * Intended for server-sided script playback. - * But since we dont need that, we just use it for timestamps. - */ -func (rs heresphereRoutes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { - scn := r.Context().Value(heresphereKey).(*models.Scene) - - // Decode event - var event HeresphereVideoEvent - err := json.NewDecoder(r.Body).Decode(&event) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Add playDuration - newTime := event.Time / 1000 - newDuration := 0.0 - if newTime > scn.ResumeTime { - newDuration += (newTime - scn.ResumeTime) - } - - // Update PlayCount - if per, err := getMinPlayPercent(); err == nil { - // Above min playback percent - if file := scn.Files.Primary(); file != nil && newTime/file.Duration > float64(per)/100.0 { - // Create update set - ret := &scene.UpdateSet{ - ID: scn.ID, - } - ret.Partial = models.NewScenePartial() - - // Unless we track playback, we cant know if its a "played" or skip event - if scn.PlayCount == 0 { - ret.Partial.PlayCount.Set = true - ret.Partial.PlayCount.Value = 1 - } - - // Update scene - if err := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - _, err := ret.Update(ctx, rs.repository.Scene) - return err - }); err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - } - } - - // Write - if _, err := rs.resolver.Mutation().SceneSaveActivity(r.Context(), strconv.Itoa(scn.ID), &newTime, &newDuration); err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) -} - -/* - * This endpoint is for letting the user update scene data - */ -func (rs heresphereRoutes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) error { - scn := r.Context().Value(heresphereKey).(*models.Scene) - user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) - shouldUpdate := false - fileDeleter := file.NewDeleter() - // Having this here is mostly paranoia - c := config.GetInstance() - - // Create update set - ret := &scene.UpdateSet{ - ID: scn.ID, - } - ret.Partial = models.NewScenePartial() - - // Update rating - if user.Rating != nil && c.GetHSPWriteRatings() { - rating := models.Rating5To100F(*user.Rating) - ret.Partial.Rating.Value = rating - ret.Partial.Rating.Set = true - shouldUpdate = true - } - - // Delete primary file - if user.DeleteFile != nil && *user.DeleteFile && c.GetHSPWriteDeletes() { - if err := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - fqb := rs.repository.File - - if err := scn.LoadPrimaryFile(ctx, fqb); err != nil { - return err - } - - ff := scn.Files.Primary() - if ff != nil { - if err := file.Destroy(ctx, fqb, ff, fileDeleter, true); err != nil { - return err - } - } - - return nil - }); err != nil { - fileDeleter.Rollback() - return err - } - shouldUpdate = true - } - - // Favorites tag - if user.IsFavorite != nil && c.GetHSPWriteFavorites() { - if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - var err error - var favTag *models.Tag - - tagId := config.GetInstance().GetHSPFavoriteTag() - if favTag, err = rs.repository.Tag.Find(ctx, tagId); err == nil { - favTagVal := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%s", favTag.Name)} - - // Do the old switcheroo to figure out how to add the tag - if *user.IsFavorite { - if user.Tags == nil { - user.Tags = &[]HeresphereVideoTag{favTagVal} - } else { - *user.Tags = append(*user.Tags, favTagVal) - } - } else { - if user.Tags == nil { - sceneTags := rs.getVideoTags(r, scn) - user.Tags = &sceneTags - } - - for i, tag := range *user.Tags { - if tag.Name == favTagVal.Name { - *user.Tags = append((*user.Tags)[:i], (*user.Tags)[i+1:]...) - break - } - } - } - - shouldUpdate = true - } - - return err - }); err != nil { - return err - } - } - - // Tags - if user.Tags != nil && c.GetHSPWriteTags() { - // Search input tags and add/create any new ones - var tagIDs []int - var perfIDs []int - - for _, tagI := range *user.Tags { - // If missing - if len(tagI.Name) == 0 { - continue - } - - // If add tag - // FUTURE IMPROVEMENT: Switch to CutPrefix as it's nicer (1.20+) - // FUTURE IMPROVEMENT: Consider batching searches - if strings.HasPrefix(tagI.Name, "Tag:") { - after := strings.TrimPrefix(tagI.Name, "Tag:") - var err error - var tagMod *models.Tag - if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - // Search for tag - tagMod, err = rs.repository.Tag.FindByName(ctx, after, true) - return err - }); err != nil { - tagMod = nil - } - - if tagMod != nil { - tagIDs = append(tagIDs, tagMod.ID) - } - continue - } - - // If add performer - if strings.HasPrefix(tagI.Name, "Performer:") { - after := strings.TrimPrefix(tagI.Name, "Performer:") - var err error - var tagMod *models.Performer - if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - var tagMods []*models.Performer - - // Search for performer - if tagMods, err = rs.repository.Performer.FindByNames(ctx, []string{after}, true); err == nil && len(tagMods) > 0 { - tagMod = tagMods[0] - } - - return err - }); err != nil { - tagMod = nil - } - - if tagMod != nil { - perfIDs = append(perfIDs, tagMod.ID) - } - continue - } - - // If add marker - if strings.HasPrefix(tagI.Name, "Marker:") { - after := strings.TrimPrefix(tagI.Name, "Marker:") - var tagId *string - if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - var err error - var markerResult []*models.MarkerStringsResultType - searchType := "count" - - // Search for marker - if markerResult, err = rs.repository.SceneMarker.GetMarkerStrings(ctx, &after, &searchType); err == nil && len(markerResult) > 0 { - tagId = &markerResult[0].ID - - // Search for tag - if markers, err := rs.repository.SceneMarker.FindBySceneID(r.Context(), scn.ID); err == nil { - i, e := strconv.Atoi(*tagId) - if e == nil { - // Note: Currently we search if a marker exists. - // If it doesn't, create it. - // This also means that markers CANNOT be deleted using the api. - for _, marker := range markers { - if marker.Seconds == tagI.Start && - marker.SceneID == scn.ID && - marker.PrimaryTagID == i { - tagId = nil - } - } - } - } - } - - return err - }); err != nil { - // Create marker - if tagId != nil { - newTag := SceneMarkerCreateInput{ - Seconds: tagI.Start, - SceneID: strconv.Itoa(scn.ID), - PrimaryTagID: *tagId, - } - if _, err := rs.resolver.Mutation().SceneMarkerCreate(context.Background(), newTag); err != nil { - return err - } - } - } - continue - } - - if strings.HasPrefix(tagI.Name, "Movie:") { - after := strings.TrimPrefix(tagI.Name, "Movie:") - - var err error - var tagMod *models.Movie - if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - // Search for performer - tagMod, err = rs.repository.Movie.FindByName(ctx, after, true) - return err - }); err == nil { - ret.Partial.MovieIDs.Mode = models.RelationshipUpdateModeSet - ret.Partial.MovieIDs.AddUnique(models.MoviesScenes{ - MovieID: tagMod.ID, - SceneIndex: &scn.ID, - }) - } - continue - } - if strings.HasPrefix(tagI.Name, "Studio:") { - after := strings.TrimPrefix(tagI.Name, "Studio:") - - var err error - var tagMod *models.Studio - if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - // Search for performer - tagMod, err = rs.repository.Studio.FindByName(ctx, after, true) - return err - }); err == nil { - ret.Partial.StudioID.Set = true - ret.Partial.StudioID.Value = tagMod.ID - } - continue - } - if strings.HasPrefix(tagI.Name, "Director:") { - after := strings.TrimPrefix(tagI.Name, "Director:") - ret.Partial.Director.Set = true - ret.Partial.Director.Value = after - continue - } - - // Custom - { - tagName := tagI.Name - - // Will be overwritten if PlayCount tag is updated - prefix := string(HeresphereCustomTagWatched) + ":" - if strings.HasPrefix(tagName, prefix) { - after := strings.TrimPrefix(tagName, prefix) - if b, err := strconv.ParseBool(after); err == nil { - // Plays chicken - if b && scn.PlayCount == 0 { - ret.Partial.PlayCount.Set = true - ret.Partial.PlayCount.Value = 1 - } else if !b { - ret.Partial.PlayCount.Set = true - ret.Partial.PlayCount.Value = 0 - } - } - continue - } - prefix = string(HeresphereCustomTagOrganized) + ":" - if strings.HasPrefix(tagName, prefix) { - after := strings.TrimPrefix(tagName, prefix) - if b, err := strconv.ParseBool(after); err == nil { - ret.Partial.Organized.Set = true - ret.Partial.Organized.Value = b - } - continue - } - prefix = string(HeresphereCustomTagRated) + ":" - if strings.HasPrefix(tagName, prefix) { - after := strings.TrimPrefix(tagName, prefix) - if b, err := strconv.ParseBool(after); err == nil && !b { - ret.Partial.Rating.Set = true - ret.Partial.Rating.Null = true - } - continue - } - - // Set numbers - prefix = string(HeresphereCustomTagPlayCount) + ":" - if strings.HasPrefix(tagName, prefix) { - after := strings.TrimPrefix(tagName, prefix) - if numRes, err := strconv.Atoi(after); err != nil { - ret.Partial.PlayCount.Set = true - ret.Partial.PlayCount.Value = numRes - } - continue - } - prefix = string(HeresphereCustomTagOCounter) + ":" - if strings.HasPrefix(tagName, prefix) { - after := strings.TrimPrefix(tagName, prefix) - if numRes, err := strconv.Atoi(after); err != nil { - ret.Partial.OCounter.Set = true - ret.Partial.OCounter.Value = numRes - } - continue - } - } - } - - // Update tags - ret.Partial.TagIDs = &models.UpdateIDs{ - IDs: tagIDs, - Mode: models.RelationshipUpdateModeSet, - } - // Update performers - ret.Partial.PerformerIDs = &models.UpdateIDs{ - IDs: perfIDs, - Mode: models.RelationshipUpdateModeSet, - } - shouldUpdate = true - } - - if shouldUpdate { - // Update scene - if err := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - _, err := ret.Update(ctx, rs.repository.Scene) - return err - }); err != nil { - return err - } - - // Commit to delete file - fileDeleter.Commit() - w.WriteHeader(http.StatusOK) - } - return nil -} - -/* - * This auxiliary function gathers various tags from the scene to feed the api. - */ -func (rs heresphereRoutes) getVideoTags(r *http.Request, scene *models.Scene) []HeresphereVideoTag { - processedTags := []HeresphereVideoTag{} - - // Load all relationships - if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - return scene.LoadRelationships(ctx, rs.repository.Scene) - }); err != nil { - return processedTags - } - - if mark_ids, err := rs.resolver.Scene().SceneMarkers(r.Context(), scene); err == nil { - for _, mark := range mark_ids { - tagName := mark.Title - - // Add tag name - if ret, err := loaders.From(r.Context()).TagByID.Load(mark.PrimaryTagID); err == nil { - if len(tagName) == 0 { - tagName = ret.Name - } else { - tagName = fmt.Sprintf("%s - %s", tagName, ret.Name) - } - } - - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Marker:%s", tagName), - Start: mark.Seconds * 1000, - End: (mark.Seconds + 60) * 1000, - } - processedTags = append(processedTags, genTag) - } - } - - if gallery_ids, err := rs.resolver.Scene().Galleries(r.Context(), scene); err == nil { - for _, gal := range gallery_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Gallery:%s", gal.GetTitle()), - } - processedTags = append(processedTags, genTag) - } - } - - if tag_ids, err := rs.resolver.Scene().Tags(r.Context(), scene); err == nil { - for _, tag := range tag_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Tag:%s", tag.Name), - } - processedTags = append(processedTags, genTag) - } - } - - if perf_ids, err := rs.resolver.Scene().Performers(r.Context(), scene); err == nil { - for _, perf := range perf_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Performer:%s", perf.Name), - } - processedTags = append(processedTags, genTag) - } - } - - if movie_ids, err := rs.resolver.Scene().Movies(r.Context(), scene); err == nil { - for _, movie := range movie_ids { - if movie.Movie != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Movie:%s", movie.Movie.Name), - } - processedTags = append(processedTags, genTag) - } - } - } - - if studio_id, err := rs.resolver.Scene().Studio(r.Context(), scene); err == nil && studio_id != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Studio:%s", studio_id.Name), - } - processedTags = append(processedTags, genTag) - } - - if interactive, err := rs.resolver.Scene().Interactive(r.Context(), scene); err == nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagInteractive), - strconv.FormatBool(interactive), - ), - } - processedTags = append(processedTags, genTag) - } - - if interactiveSpeed, err := rs.resolver.Scene().InteractiveSpeed(r.Context(), scene); err == nil && interactiveSpeed != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Funspeed:%d", - *interactiveSpeed, - ), - } - processedTags = append(processedTags, genTag) - } - - if len(scene.Director) > 0 { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Director:%s", scene.Director), - } - processedTags = append(processedTags, genTag) - } - - if scene.Rating != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Rating:%d", - models.Rating100To5(*scene.Rating), - ), - } - processedTags = append(processedTags, genTag) - } - - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagWatched), - strconv.FormatBool(scene.PlayCount > 0), - ), - } - processedTags = append(processedTags, genTag) - } - - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagOrganized), - strconv.FormatBool(scene.Organized), - ), - } - processedTags = append(processedTags, genTag) - } - - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagRated), - strconv.FormatBool(scene.Rating != nil), - ), - } - processedTags = append(processedTags, genTag) - } - - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagOrgasmed), - strconv.FormatBool(scene.OCounter > 0), - ), - } - processedTags = append(processedTags, genTag) - } - - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagPlayCount), scene.PlayCount), - } - processedTags = append(processedTags, genTag) - } - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagOCounter), scene.OCounter), - } - processedTags = append(processedTags, genTag) - } - - return processedTags -} - -/* - * This auxiliary function gathers a script if applicable - */ -func (rs heresphereRoutes) getVideoScripts(r *http.Request, scene *models.Scene) []HeresphereVideoScript { - processedScripts := []HeresphereVideoScript{} - - if interactive, err := rs.resolver.Scene().Interactive(r.Context(), scene); err == nil && interactive { - processedScript := HeresphereVideoScript{ - Name: "Default script", - Url: addApiKey(urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetFunscriptURL()), - Rating: 5, - } - processedScripts = append(processedScripts, processedScript) - } - - return processedScripts -} - -/* - * This auxiliary function gathers subtitles if applicable - */ -func (rs heresphereRoutes) getVideoSubtitles(r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { - processedSubtitles := []HeresphereVideoSubtitle{} - - if captions_id, err := rs.resolver.Scene().Captions(r.Context(), scene); err == nil { - for _, caption := range captions_id { - processedCaption := HeresphereVideoSubtitle{ - Name: caption.Filename, - Language: caption.LanguageCode, - Url: addApiKey(fmt.Sprintf("%s?lang=%s&type=%s", - urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetCaptionURL(), - caption.LanguageCode, - caption.CaptionType, - )), - } - processedSubtitles = append(processedSubtitles, processedCaption) - } - } - - return processedSubtitles -} - -/* - * This auxiliary function gathers media information + transcoding options. - */ -func (rs heresphereRoutes) getVideoMedia(r *http.Request, scene *models.Scene) []HeresphereVideoMedia { - processedMedia := []HeresphereVideoMedia{} - - // Codec by source map - mediaTypes := make(map[string][]HeresphereVideoMediaSource) - - // Load media file - if err := txn.WithTxn(r.Context(), rs.repository.TxnManager, func(ctx context.Context) error { - return scene.LoadPrimaryFile(ctx, rs.repository.File) - }); err != nil { - return processedMedia - } - - // If valid primary file - if mediaFile := scene.Files.Primary(); mediaFile != nil { - // Get source URL - sourceUrl := urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamURL("").String() - processedEntry := HeresphereVideoMediaSource{ - Resolution: mediaFile.Height, - Height: mediaFile.Height, - Width: mediaFile.Width, - Size: mediaFile.Size, - Url: addApiKey(sourceUrl), - } - processedMedia = append(processedMedia, HeresphereVideoMedia{ - Name: "direct stream", - Sources: []HeresphereVideoMediaSource{processedEntry}, - }) - - // Add transcodes - resRatio := float32(mediaFile.Width) / float32(mediaFile.Height) - transcodeSize := config.GetInstance().GetMaxStreamingTranscodeSize() - transNames := []string{"HLS", "DASH"} - for i, trans := range []string{".m3u8", ".mpd"} { - for _, res := range models.AllStreamingResolutionEnum { - maxTrans := transcodeSize.GetMaxResolution() - // If resolution is below or equal to allowed res (and original video res) - if height := res.GetMaxResolution(); (maxTrans == 0 || maxTrans >= height) && height <= mediaFile.Height { - processedEntry.Resolution = height - processedEntry.Height = height - processedEntry.Width = int(resRatio * float32(height)) - processedEntry.Size = 0 - // Resolution 0 means original - if height == 0 { - processedEntry.Resolution = mediaFile.Height - processedEntry.Height = mediaFile.Height - processedEntry.Width = mediaFile.Width - } - processedEntry.Url = addApiKey(fmt.Sprintf("%s%s?resolution=%s", sourceUrl, trans, res.String())) - - typeName := transNames[i] - mediaTypes[typeName] = append(mediaTypes[typeName], processedEntry) - } - } - } - } - - // Reconstruct tables - for codec, sources := range mediaTypes { - processedMedia = append(processedMedia, HeresphereVideoMedia{ - Name: codec, - Sources: sources, - }) - } - - return processedMedia -} - -/* - * This endpoint provides the main libraries that are available to browse. - */ -func (rs heresphereRoutes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { - // Banner - banner := HeresphereBanner{ - Image: fmt.Sprintf("%s%s", - GetBaseURL(r), - "/apple-touch-icon.png", - ), - Link: fmt.Sprintf("%s%s", - GetBaseURL(r), - "/", - ), - } - - // Read scenes - var scenes []*models.Scene - if err := txn.WithReadTxn(r.Context(), rs.repository.TxnManager, func(ctx context.Context) error { - var err error - scenes, err = rs.repository.Scene.All(ctx) - return err - }); err != nil { - http.Error(w, "Failed to fetch scenes!", http.StatusInternalServerError) - return - } - - // Create scene list - sceneUrls := make([]string, len(scenes)) - for idx, scene := range scenes { - sceneUrls[idx] = fmt.Sprintf("%s/heresphere/%d", - GetBaseURL(r), - scene.ID, - ) - } - - // All library - library := HeresphereIndexEntry{ - Name: "All", - List: sceneUrls, - } - // Index - idx := HeresphereIndex{ - Access: HeresphereMember, - Banner: banner, - Library: []HeresphereIndexEntry{library}, - } - - // Create a JSON encoder for the response writer - w.Header().Set("Content-Type", "application/json") - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - if err := enc.Encode(idx); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } -} - -/* - * This auxiliary function finds vr projection modes from tags and the filename. - */ -func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntry) { - // Detect VR modes from tags - for _, tag := range processedScene.Tags { - tagPre := strings.TrimPrefix(tag.Name, "Tag:") - - // Has degrees tag - if strings.HasSuffix(tagPre, "°") { - deg := strings.TrimSuffix(tagPre, "°") - if s, err := strconv.ParseFloat(deg, 64); err == nil { - processedScene.Fov = s - } - } - // Has VR tag - vrTag, err := getVrTag() - if err == nil && tagPre == vrTag { - if processedScene.Projection == HeresphereProjectionPerspective { - processedScene.Projection = HeresphereProjectionEquirectangular - } - if processedScene.Stereo == HeresphereStereoMono { - processedScene.Stereo = HeresphereStereoSbs - } - } - // Has Fisheye tag - if tagPre == "Fisheye" { - processedScene.Projection = HeresphereProjectionFisheye - if processedScene.Stereo == HeresphereStereoMono { - processedScene.Stereo = HeresphereStereoSbs - } - } - } - - // Detect VR modes from filename - file := scene.Files.Primary() - if file != nil { - path := strings.ToUpper(file.Basename) - - // Stereo settings - if strings.Contains(path, "_LR") || strings.Contains(path, "_3DH") { - processedScene.Stereo = HeresphereStereoSbs - } - if strings.Contains(path, "_RL") { - processedScene.Stereo = HeresphereStereoSbs - processedScene.IsEyeSwapped = true - } - if strings.Contains(path, "_TB") || strings.Contains(path, "_3DV") { - processedScene.Stereo = HeresphereStereoTB - } - if strings.Contains(path, "_BT") { - processedScene.Stereo = HeresphereStereoTB - processedScene.IsEyeSwapped = true - } - - // Projection settings - if strings.Contains(path, "_EAC360") || strings.Contains(path, "_360EAC") { - processedScene.Projection = HeresphereProjectionEquirectangularCubemap - processedScene.Fov = 360.0 - } - if strings.Contains(path, "_360") { - processedScene.Projection = HeresphereProjectionEquirectangular360 - processedScene.Fov = 360.0 - } - if strings.Contains(path, "_F180") || strings.Contains(path, "_180F") || strings.Contains(path, "_VR180") { - processedScene.Projection = HeresphereProjectionFisheye - processedScene.Fov = 180.0 - } else if strings.Contains(path, "_180") { - processedScene.Projection = HeresphereProjectionEquirectangular - processedScene.Fov = 180.0 - } - if strings.Contains(path, "_MKX200") { - processedScene.Projection = HeresphereProjectionFisheye - processedScene.Fov = 200.0 - processedScene.Lens = HeresphereLensMKX200 - } - if strings.Contains(path, "_MKX220") { - processedScene.Projection = HeresphereProjectionFisheye - processedScene.Fov = 220.0 - processedScene.Lens = HeresphereLensMKX220 - } - if strings.Contains(path, "_FISHEYE") { - processedScene.Projection = HeresphereProjectionFisheye - } - if strings.Contains(path, "_RF52") || strings.Contains(path, "_FISHEYE190") { - processedScene.Projection = HeresphereProjectionFisheye - processedScene.Fov = 190.0 - } - if strings.Contains(path, "_VRCA220") { - processedScene.Projection = HeresphereProjectionFisheye - processedScene.Fov = 220.0 - processedScene.Lens = HeresphereLensVRCA220 - } - } -} - -/* - * This endpoint provides a single scenes full information. - */ -func (rs heresphereRoutes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { - user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) - c := config.GetInstance() - - // Update request - if err := rs.HeresphereVideoDataUpdate(w, r); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Fetch scene - scene := r.Context().Value(heresphereKey).(*models.Scene) - - // Load relationships - processedScene := HeresphereVideoEntry{} - if err := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - return scene.LoadRelationships(ctx, rs.repository.Scene) - }); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Create scene - processedScene = HeresphereVideoEntry{ - Access: HeresphereMember, - Title: scene.GetTitle(), - Description: scene.Details, - ThumbnailImage: addApiKey(urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetScreenshotURL()), - ThumbnailVideo: addApiKey(urlbuilders.NewSceneURLBuilder(GetBaseURL(r), scene).GetStreamPreviewURL()), - DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60000.0, - Rating: 0, - Favorites: 0, - Comments: scene.OCounter, - IsFavorite: rs.getVideoFavorite(r, scene), - Projection: HeresphereProjectionPerspective, - Stereo: HeresphereStereoMono, - IsEyeSwapped: false, - Fov: 180.0, - Lens: HeresphereLensLinear, - CameraIPD: 6.5, - EventServer: addApiKey(fmt.Sprintf("%s/heresphere/%d/event", - GetBaseURL(r), - scene.ID, - )), - Scripts: rs.getVideoScripts(r, scene), - Subtitles: rs.getVideoSubtitles(r, scene), - Tags: rs.getVideoTags(r, scene), - Media: []HeresphereVideoMedia{}, - WriteFavorite: c.GetHSPWriteFavorites(), - WriteRating: c.GetHSPWriteRatings(), - WriteTags: c.GetHSPWriteTags(), - WriteHSP: false, - } - - // Find projection options - FindProjectionTags(scene, &processedScene) - - // Additional info - if user.NeedsMediaSource != nil && *user.NeedsMediaSource { - processedScene.Media = rs.getVideoMedia(r, scene) - } - if scene.Date != nil { - processedScene.DateReleased = scene.Date.Format("2006-01-02") - } - if scene.Rating != nil { - fiveScale := models.Rating100To5F(*scene.Rating) - processedScene.Rating = fiveScale - } - if processedScene.IsFavorite { - processedScene.Favorites++ - } - if scene.Files.PrimaryLoaded() { - file_ids := scene.Files.Primary() - if file_ids != nil { - processedScene.Duration = handleFloat64Value(file_ids.Duration * 1000.0) - } - } - - // Create a JSON encoder for the response writer - w.Header().Set("Content-Type", "application/json") - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - if err := enc.Encode(processedScene); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } -} - -/* - * This auxiliary function finds if a login is needed, and auth is correct. - */ -func basicLogin(username string, password string) bool { - // If needs creds, try login - if config.GetInstance().HasCredentials() { - err := manager.GetInstance().SessionStore.LoginPlain(username, password) - return err != nil - } - return false -} - -/* - * This endpoint function allows the user to login and receive a token if successful. - */ -func (rs heresphereRoutes) HeresphereLoginToken(w http.ResponseWriter, r *http.Request) { - user := r.Context().Value(heresphereUserKey).(HeresphereAuthReq) - - // Try login - if basicLogin(user.Username, user.Password) { - writeNotAuthorized(w, r, "Invalid credentials") - return - } - - // Fetch key - key := config.GetInstance().GetAPIKey() - if len(key) == 0 { - writeNotAuthorized(w, r, "Missing auth key!") - return - } - - // Generate auth response - auth := &HeresphereAuthResp{ - AuthToken: key, - Access: HeresphereMember, - } - - // Create a JSON encoder for the response writer - w.Header().Set("Content-Type", "application/json") - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - if err := enc.Encode(auth); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } -} - -/* - * This auxiliary function finds if the request has a valid auth token. - */ -func HeresphereHasValidToken(r *http.Request) bool { - // Check header auth - apiKey := r.Header.Get(HeresphereAuthHeader) - - // Check url query auth - if len(apiKey) == 0 { - apiKey = r.URL.Query().Get(session.ApiKeyParameter) - } - - return len(apiKey) > 0 && apiKey == config.GetInstance().GetAPIKey() -} - -/* - * This auxiliary function adds an auth token to a url - */ -func addApiKey(urlS string) string { - // Parse URL - u, err := url.Parse(urlS) - if err != nil { - // shouldn't happen - panic(err) - } - - // Add apikey if applicable - if config.GetInstance().GetAPIKey() != "" { - v := u.Query() - if !v.Has("apikey") { - v.Set("apikey", config.GetInstance().GetAPIKey()) - } - u.RawQuery = v.Encode() - } - - return u.String() -} - -/* - * This auxiliary writes a library with a fake name upon auth failure - */ -func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { - // Banner - banner := HeresphereBanner{ - Image: fmt.Sprintf("%s%s", - GetBaseURL(r), - "/apple-touch-icon.png", - ), - Link: fmt.Sprintf("%s%s", - GetBaseURL(r), - "/", - ), - } - // Default video - library := HeresphereIndexEntry{ - Name: msg, - List: []string{}, - } - // Index - idx := HeresphereIndex{ - Access: HeresphereBadLogin, - Banner: banner, - Library: []HeresphereIndexEntry{library}, - } - - // Create a JSON encoder for the response writer - w.Header().Set("Content-Type", "application/json") - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - if err := enc.Encode(idx); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } -} - -/* - * This context function finds the applicable scene from the request and stores it. - */ -func (rs heresphereRoutes) HeresphereSceneCtx(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Get sceneId - sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) - if err != nil { - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - - // Resolve scene - var scene *models.Scene - _ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { - qb := rs.sceneFinder - scene, _ = qb.Find(ctx, sceneID) - - if scene != nil { - // A valid scene should have a attached video - if err := scene.LoadPrimaryFile(ctx, rs.fileFinder); err != nil { - if !errors.Is(err, context.Canceled) { - logger.Errorf("error loading primary file for scene %d: %v", sceneID, err) - } - // set scene to nil so that it doesn't try to use the primary file - scene = nil - } - } - - return nil - }) - if scene == nil { - http.Error(w, http.StatusText(404), 404) - return - } - - ctx := context.WithValue(r.Context(), heresphereKey, scene) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -/* - * This context function finds if the authentication is correct, otherwise rejects the request. - */ -func (rs heresphereRoutes) HeresphereCtx(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Add JSON Header (using Add uses camel case and makes it invalid because "Json") - w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} - - // Only if enabled - if !config.GetInstance().GetHSPDefaultEnabled() { - writeNotAuthorized(w, r, "HereSphere API not enabled!") - return - } - - // Read HTTP Body - body, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, "can't read body", http.StatusBadRequest) - return - } - // Make the Body re-readable (afaik only /event uses this) - r.Body = io.NopCloser(bytes.NewBuffer(body)) - - // Auth enabled and not has valid credentials (TLDR: needs to be blocked) - isAuth := config.GetInstance().HasCredentials() && !HeresphereHasValidToken(r) - - // Default request - user := HeresphereAuthReq{} - - // Attempt decode, and if err and invalid auth, fail - if err := json.Unmarshal(body, &user); err != nil && isAuth { - writeNotAuthorized(w, r, "Not logged in!") - return - } - - // If empty, fill as true - if user.NeedsMediaSource == nil { - needsMedia := true - user.NeedsMediaSource = &needsMedia - } - - // If invalid creds, only allow auth endpoint - if isAuth && !strings.HasPrefix(r.URL.Path, "/heresphere/auth") { - writeNotAuthorized(w, r, "Unauthorized!") - return - } - - ctx := context.WithValue(r.Context(), heresphereUserKey, user) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} diff --git a/internal/api/server.go b/internal/api/server.go index 9b2ec4463d2..7c1a6db9e21 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -30,6 +30,7 @@ import ( "github.com/go-chi/httplog" "github.com/stashapp/stash/internal/api/loaders" "github.com/stashapp/stash/internal/build" + "github.com/stashapp/stash/internal/heresphere" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/pkg/fsutil" @@ -140,7 +141,7 @@ func Start() error { r.HandleFunc(gqlEndpoint, gqlHandlerFunc) r.HandleFunc(playgroundEndpoint, func(w http.ResponseWriter, r *http.Request) { setPageSecurityHeaders(w, r) - endpoint := getProxyPrefix(r) + gqlEndpoint + endpoint := manager.GetProxyPrefix(r) + gqlEndpoint gqlPlayground.Handler("GraphQL playground", endpoint)(w, r) }) @@ -174,13 +175,11 @@ func Start() error { tagFinder: txnManager.Tag, }.Routes()) r.Mount("/downloads", downloadsRoutes{}.Routes()) - r.Mount("/heresphere", heresphereRoutes{ - txnManager: txnManager, - sceneFinder: txnManager.Scene, - fileFinder: txnManager.File, - captionFinder: txnManager.File, - repository: txnManager, - resolver: resolver, + r.Mount("/heresphere", heresphere.Routes{ + TxnManager: txnManager, + SceneFinder: txnManager.Scene, + FileFinder: txnManager.File, + Repository: txnManager, }.Routes()) r.HandleFunc("/css", cssHandler(c, pluginCache)) @@ -230,7 +229,7 @@ func Start() error { } indexHtml := string(data) - prefix := getProxyPrefix(r) + prefix := manager.GetProxyPrefix(r) indexHtml = strings.ReplaceAll(indexHtml, "%COLOR%", themeColor) indexHtml = strings.Replace(indexHtml, ` 0 && apiKey == config.GetInstance().GetAPIKey() +} + +/* + * This auxiliary function adds an auth token to a url + */ +func addApiKey(urlS string) string { + // Parse URL + u, err := url.Parse(urlS) + if err != nil { + // shouldn't happen + panic(err) + } + + // Add apikey if applicable + if config.GetInstance().GetAPIKey() != "" { + v := u.Query() + if !v.Has("apikey") { + v.Set("apikey", config.GetInstance().GetAPIKey()) + } + u.RawQuery = v.Encode() + } + + return u.String() +} + +/* + * This auxiliary writes a library with a fake name upon auth failure + */ +func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { + // Banner + banner := HeresphereBanner{ + Image: fmt.Sprintf("%s%s", + manager.GetBaseURL(r), + "/apple-touch-icon.png", + ), + Link: fmt.Sprintf("%s%s", + manager.GetBaseURL(r), + "/", + ), + } + // Default video + library := HeresphereIndexEntry{ + Name: msg, + List: []string{}, + } + // Index + idx := HeresphereIndex{ + Access: HeresphereBadLogin, + Banner: banner, + Library: []HeresphereIndexEntry{library}, + } + + // Create a JSON encoder for the response writer + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + if err := enc.Encode(idx); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} diff --git a/internal/heresphere/favorite.go b/internal/heresphere/favorite.go new file mode 100644 index 00000000000..f903694ca7b --- /dev/null +++ b/internal/heresphere/favorite.go @@ -0,0 +1,80 @@ +package heresphere + +import ( + "context" + "fmt" + "net/http" + + "github.com/stashapp/stash/internal/manager/config" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/scene" + "github.com/stashapp/stash/pkg/txn" +) + +/* + * Searches for favorite tag if it exists, otherwise adds it. + * This adds a tag, which means tags must also be enabled, or it will never be written. + */ +func handleFavoriteTag(ctx context.Context, rs Routes, scn *models.Scene, user HeresphereAuthReq, txnManager txn.Manager, ret *scene.UpdateSet) (bool, error) { + if err := txn.WithReadTxn(ctx, txnManager, func(ctx context.Context) error { + var err error + var favTag *models.Tag + + tagId := config.GetInstance().GetHSPFavoriteTag() + if favTag, err = rs.Repository.Tag.Find(ctx, tagId); err == nil { + favTagVal := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%s", favTag.Name)} + + // Do the old switcheroo to figure out how to add the tag + if *user.IsFavorite { + if user.Tags == nil { + user.Tags = &[]HeresphereVideoTag{favTagVal} + } else { + *user.Tags = append(*user.Tags, favTagVal) + } + } else { + if user.Tags == nil { + sceneTags := getVideoTags(rs, ctx, scn) + user.Tags = &sceneTags + } + + for i, tag := range *user.Tags { + if tag.Name == favTagVal.Name { + *user.Tags = append((*user.Tags)[:i], (*user.Tags)[i+1:]...) + break + } + } + } + + return nil + } + + return err + }); err != nil { + return false, err + } + + return true, nil +} + +/* + * This auxiliary function searches for the "favorite" tag + */ +func getVideoFavorite(rs Routes, r *http.Request, scene *models.Scene) bool { + found := false + favTag := config.GetInstance().GetHSPFavoriteTag() + + txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + tag_ids, err := rs.Repository.Tag.FindBySceneID(ctx, scene.ID) + if err == nil { + for _, tag := range tag_ids { + if tag.ID == favTag { + found = true + return nil + } + } + } + return err + }) + + return found +} diff --git a/internal/heresphere/interfaces.go b/internal/heresphere/interfaces.go new file mode 100644 index 00000000000..44f3c928121 --- /dev/null +++ b/internal/heresphere/interfaces.go @@ -0,0 +1,10 @@ +package heresphere + +import ( + "github.com/stashapp/stash/pkg/scene" +) + +type sceneFinder interface { + scene.Queryer + scene.IDFinder +} diff --git a/internal/heresphere/keys.go b/internal/heresphere/keys.go new file mode 100644 index 00000000000..185a7ad182a --- /dev/null +++ b/internal/heresphere/keys.go @@ -0,0 +1,8 @@ +package heresphere + +type key int + +const ( + sceneKey = iota + 1 + authKey +) diff --git a/internal/heresphere/media.go b/internal/heresphere/media.go new file mode 100644 index 00000000000..18e091e66de --- /dev/null +++ b/internal/heresphere/media.go @@ -0,0 +1,162 @@ +package heresphere + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "github.com/stashapp/stash/internal/api/urlbuilders" + "github.com/stashapp/stash/internal/manager" + "github.com/stashapp/stash/internal/manager/config" + "github.com/stashapp/stash/pkg/file" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/txn" +) + +/* + * Returns the primary media source + */ +func getPrimaryMediaSource(rs Routes, r *http.Request, scene *models.Scene) HeresphereVideoMediaSource { + mediaFile := scene.Files.Primary() + if mediaFile == nil { + return HeresphereVideoMediaSource{} // Return empty source if no primary file + } + + sourceUrl := urlbuilders.NewSceneURLBuilder(manager.GetBaseURL(r), scene).GetStreamURL("").String() + sourceUrlWithApiKey := addApiKey(sourceUrl) + + return HeresphereVideoMediaSource{ + Resolution: mediaFile.Height, + Height: mediaFile.Height, + Width: mediaFile.Width, + Size: mediaFile.Size, + Url: sourceUrlWithApiKey, + } +} + +/* + * This auxiliary function gathers a script if applicable + */ +func getVideoScripts(rs Routes, r *http.Request, scene *models.Scene) []HeresphereVideoScript { + processedScripts := []HeresphereVideoScript{} + + primaryFile := scene.Files.Primary() + if primaryFile != nil && primaryFile.Interactive { + processedScript := HeresphereVideoScript{ + Name: "Default script", + Url: addApiKey(urlbuilders.NewSceneURLBuilder(manager.GetBaseURL(r), scene).GetFunscriptURL()), + Rating: 5, + } + processedScripts = append(processedScripts, processedScript) + } + + return processedScripts +} + +/* + * This auxiliary function gathers subtitles if applicable + */ +func getVideoSubtitles(rs Routes, r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { + processedSubtitles := []HeresphereVideoSubtitle{} + + txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + var err error + + primaryFile := scene.Files.Primary() + if primaryFile != nil { + if captions_id, err := rs.Repository.File.GetCaptions(ctx, primaryFile.ID); err == nil { + for _, caption := range captions_id { + processedCaption := HeresphereVideoSubtitle{ + Name: caption.Filename, + Language: caption.LanguageCode, + Url: addApiKey(fmt.Sprintf("%s?lang=%s&type=%s", + urlbuilders.NewSceneURLBuilder(manager.GetBaseURL(r), scene).GetCaptionURL(), + caption.LanguageCode, + caption.CaptionType, + )), + } + processedSubtitles = append(processedSubtitles, processedCaption) + } + } + } + + return err + }) + + return processedSubtitles +} + +/* + * Function to get transcoded media sources + */ +func getTranscodedMediaSources(sceneURL string, transcodeSize int, mediaFile *file.VideoFile) map[string][]HeresphereVideoMediaSource { + transcodedSources := make(map[string][]HeresphereVideoMediaSource) + transNames := []string{"HLS", "DASH"} + resRatio := float32(mediaFile.Width) / float32(mediaFile.Height) + + for i, trans := range []string{".m3u8", ".mpd"} { + for _, res := range models.AllStreamingResolutionEnum { + if transcodeSize == 0 || transcodeSize >= res.GetMaxResolution() { + if height := res.GetMaxResolution(); height <= mediaFile.Height { + transcodedUrl, err := url.Parse(sceneURL + trans) + if err != nil { + panic(err) + } + transcodedUrl.Query().Add("resolution", res.String()) + + processedEntry := HeresphereVideoMediaSource{ + Resolution: height, + Height: height, + Width: int(resRatio * float32(height)), + Size: 0, + Url: transcodedUrl.String(), + } + + typeName := transNames[i] + transcodedSources[typeName] = append(transcodedSources[typeName], processedEntry) + } + } + } + } + + return transcodedSources +} + +/* + * Main function to gather media information and transcoding options + */ +func getVideoMedia(rs Routes, r *http.Request, scene *models.Scene) []HeresphereVideoMedia { + processedMedia := []HeresphereVideoMedia{} + + if err := txn.WithTxn(r.Context(), rs.Repository.TxnManager, func(ctx context.Context) error { + return scene.LoadPrimaryFile(ctx, rs.Repository.File) + }); err != nil { + return processedMedia + } + + primarySource := getPrimaryMediaSource(rs, r, scene) + if primarySource.Url != "" { + processedMedia = append(processedMedia, HeresphereVideoMedia{ + Name: "direct stream", + Sources: []HeresphereVideoMediaSource{primarySource}, + }) + } + + mediaFile := scene.Files.Primary() + if mediaFile != nil { + sceneURL := urlbuilders.NewSceneURLBuilder(manager.GetBaseURL(r), scene).GetStreamURL(config.GetInstance().GetAPIKey()).String() + transcodeSize := config.GetInstance().GetMaxStreamingTranscodeSize().GetMaxResolution() + transcodedSources := getTranscodedMediaSources(sceneURL, transcodeSize, mediaFile) + + // Reconstruct tables for transcoded sources + for codec, sources := range transcodedSources { + processedMedia = append(processedMedia, HeresphereVideoMedia{ + Name: codec, + Sources: sources, + }) + } + } + + return processedMedia +} diff --git a/internal/heresphere/projections.go b/internal/heresphere/projections.go new file mode 100644 index 00000000000..ce21d30d350 --- /dev/null +++ b/internal/heresphere/projections.go @@ -0,0 +1,116 @@ +package heresphere + +import ( + "strconv" + "strings" + + "github.com/stashapp/stash/pkg/models" +) + +/* + * Reads relevant VR tags and sets projection settings + */ +func findProjectionTagsFromTags(processedScene *HeresphereVideoEntry, tags []HeresphereVideoTag) { + for _, tag := range tags { + tagPre := strings.TrimPrefix(tag.Name, "Tag:") + + // Has degrees tag + if strings.HasSuffix(tagPre, "°") { + deg := strings.TrimSuffix(tagPre, "°") + if s, err := strconv.ParseFloat(deg, 64); err == nil { + processedScene.Fov = s + } + } + // Has VR tag + vrTag, err := getVrTag() + if err == nil && tagPre == vrTag { + if processedScene.Projection == HeresphereProjectionPerspective { + processedScene.Projection = HeresphereProjectionEquirectangular + } + if processedScene.Stereo == HeresphereStereoMono { + processedScene.Stereo = HeresphereStereoSbs + } + } + // Has Fisheye tag + if tagPre == "Fisheye" { + processedScene.Projection = HeresphereProjectionFisheye + if processedScene.Stereo == HeresphereStereoMono { + processedScene.Stereo = HeresphereStereoSbs + } + } + } +} + +/* + * Reads relevant VR strings from a filename and sets projection settings + */ +func findProjectionTagsFromFilename(processedScene *HeresphereVideoEntry, filename string) { + path := strings.ToUpper(filename) + + // Stereo settings + if strings.Contains(path, "_LR") || strings.Contains(path, "_3DH") { + processedScene.Stereo = HeresphereStereoSbs + } + if strings.Contains(path, "_RL") { + processedScene.Stereo = HeresphereStereoSbs + processedScene.IsEyeSwapped = true + } + if strings.Contains(path, "_TB") || strings.Contains(path, "_3DV") { + processedScene.Stereo = HeresphereStereoTB + } + if strings.Contains(path, "_BT") { + processedScene.Stereo = HeresphereStereoTB + processedScene.IsEyeSwapped = true + } + + // Projection settings + if strings.Contains(path, "_EAC360") || strings.Contains(path, "_360EAC") { + processedScene.Projection = HeresphereProjectionEquirectangularCubemap + processedScene.Fov = 360.0 + } + if strings.Contains(path, "_360") { + processedScene.Projection = HeresphereProjectionEquirectangular360 + processedScene.Fov = 360.0 + } + if strings.Contains(path, "_F180") || strings.Contains(path, "_180F") || strings.Contains(path, "_VR180") { + processedScene.Projection = HeresphereProjectionFisheye + processedScene.Fov = 180.0 + } else if strings.Contains(path, "_180") { + processedScene.Projection = HeresphereProjectionEquirectangular + processedScene.Fov = 180.0 + } + if strings.Contains(path, "_MKX200") { + processedScene.Projection = HeresphereProjectionFisheye + processedScene.Fov = 200.0 + processedScene.Lens = HeresphereLensMKX200 + } + if strings.Contains(path, "_MKX220") { + processedScene.Projection = HeresphereProjectionFisheye + processedScene.Fov = 220.0 + processedScene.Lens = HeresphereLensMKX220 + } + if strings.Contains(path, "_FISHEYE") { + processedScene.Projection = HeresphereProjectionFisheye + } + if strings.Contains(path, "_RF52") || strings.Contains(path, "_FISHEYE190") { + processedScene.Projection = HeresphereProjectionFisheye + processedScene.Fov = 190.0 + } + if strings.Contains(path, "_VRCA220") { + processedScene.Projection = HeresphereProjectionFisheye + processedScene.Fov = 220.0 + processedScene.Lens = HeresphereLensVRCA220 + } +} + +/* + * This auxiliary function finds vr projection modes from tags and the filename. + */ +func FindProjectionTags(scene *models.Scene, processedScene *HeresphereVideoEntry) { + findProjectionTagsFromTags(processedScene, processedScene.Tags) + + file := scene.Files.Primary() + if file != nil { + findProjectionTagsFromFilename(processedScene, file.Basename) + } +} diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go new file mode 100644 index 00000000000..20eb44cc623 --- /dev/null +++ b/internal/heresphere/routes.go @@ -0,0 +1,456 @@ +package heresphere + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "strings" + + "github.com/go-chi/chi" + "github.com/stashapp/stash/internal/api/urlbuilders" + "github.com/stashapp/stash/internal/manager" + "github.com/stashapp/stash/internal/manager/config" + "github.com/stashapp/stash/pkg/file" + "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/plugin" + "github.com/stashapp/stash/pkg/scene" + "github.com/stashapp/stash/pkg/txn" +) + +type HeresphereCustomTag string + +const ( + HeresphereCustomTagInteractive HeresphereCustomTag = "Interactive" + + HeresphereCustomTagPlayCount HeresphereCustomTag = "PlayCount" + HeresphereCustomTagWatched HeresphereCustomTag = "Watched" + + HeresphereCustomTagOrganized HeresphereCustomTag = "Organized" + + HeresphereCustomTagOCounter HeresphereCustomTag = "OCounter" + HeresphereCustomTagOrgasmed HeresphereCustomTag = "Orgasmed" + + HeresphereCustomTagRated HeresphereCustomTag = "Rated" +) + +type hookExecutor interface { + ExecutePostHooks(ctx context.Context, id int, hookType plugin.HookTriggerEnum, input interface{}, inputFields []string) +} + +type Routes struct { + TxnManager txn.Manager + SceneFinder sceneFinder + FileFinder file.Finder + Repository manager.Repository +} + +/* + * This function provides the possible routes for this api. + */ +func (rs Routes) Routes() chi.Router { + r := chi.NewRouter() + + r.Route("/", func(r chi.Router) { + r.Use(rs.HeresphereCtx) + + r.Post("/", rs.HeresphereIndex) + r.Get("/", rs.HeresphereIndex) + r.Head("/", rs.HeresphereIndex) + + r.Post("/auth", rs.HeresphereLoginToken) + r.Route("/{sceneId}", func(r chi.Router) { + r.Use(rs.HeresphereSceneCtx) + + r.Post("/", rs.HeresphereVideoData) + r.Get("/", rs.HeresphereVideoData) + + r.Post("/event", rs.HeresphereVideoEvent) + }) + }) + + return r +} + +/* + * This is a video playback event + * Intended for server-sided script playback. + * But since we dont need that, we just use it for timestamps. + */ +func (rs Routes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { + scn := r.Context().Value(sceneKey).(*models.Scene) + + var event HeresphereVideoEvent + err := json.NewDecoder(r.Body).Decode(&event) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // This is kinda sketchy, since we dont know if the user skipped ahead + newTime := event.Time / 1000 + newDuration := 0.0 + if newTime > scn.ResumeTime { + newDuration += (newTime - scn.ResumeTime) + } + + if err := updatePlayCount(r.Context(), scn, event, rs.TxnManager, rs.Repository.Scene); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + _, err := rs.Repository.Scene.SaveActivity(ctx, scn.ID, &newTime, &newDuration) + return err + }); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} + +/* + * This endpoint is for letting the user update scene data + */ +func (rs Routes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) error { + scn := r.Context().Value(sceneKey).(*models.Scene) + user := r.Context().Value(authKey).(HeresphereAuthReq) + fileDeleter := file.NewDeleter() + c := config.GetInstance() + shouldUpdate := false + + ret := &scene.UpdateSet{ + ID: scn.ID, + Partial: models.NewScenePartial(), + } + + var b bool + var err error + if user.Rating != nil && c.GetHSPWriteRatings() { + if b, err = updateRating(user, ret); err != nil { + fileDeleter.Rollback() + return err + } + shouldUpdate = b || shouldUpdate + } + + if user.DeleteFile != nil && *user.DeleteFile && c.GetHSPWriteDeletes() { + if b, err = handleDeletePrimaryFile(r.Context(), rs.TxnManager, scn, rs.Repository.File, fileDeleter); err != nil { + fileDeleter.Rollback() + return err + } + shouldUpdate = b || shouldUpdate + } + + if user.IsFavorite != nil && c.GetHSPWriteFavorites() { + if b, err = handleFavoriteTag(r.Context(), rs, scn, user, rs.TxnManager, ret); err != nil { + return err + } + shouldUpdate = b || shouldUpdate + } + + if user.Tags != nil && c.GetHSPWriteTags() { + if b, err = handleTags(user.Tags, r.Context(), scn, user, rs, ret); err != nil { + return err + } + shouldUpdate = b || shouldUpdate + } + + if shouldUpdate { + if err := txn.WithTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + _, err := ret.Update(ctx, rs.Repository.Scene) + return err + }); err != nil { + return err + } + + fileDeleter.Commit() + return nil + } + return nil +} + +/* + * This endpoint provides the main libraries that are available to browse. + */ +func (rs Routes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { + // Banner + banner := HeresphereBanner{ + Image: fmt.Sprintf("%s%s", + manager.GetBaseURL(r), + "/apple-touch-icon.png", + ), + Link: fmt.Sprintf("%s%s", + manager.GetBaseURL(r), + "/", + ), + } + + // Read scenes + var scenes []*models.Scene + if err := txn.WithReadTxn(r.Context(), rs.Repository.TxnManager, func(ctx context.Context) error { + var err error + scenes, err = rs.Repository.Scene.All(ctx) + return err + }); err != nil { + http.Error(w, "Failed to fetch scenes!", http.StatusInternalServerError) + return + } + + // Create scene list + sceneUrls := make([]string, len(scenes)) + for idx, scene := range scenes { + sceneUrls[idx] = fmt.Sprintf("%s/heresphere/%d", + manager.GetBaseURL(r), + scene.ID, + ) + } + + // All library + library := HeresphereIndexEntry{ + Name: "All", + List: sceneUrls, + } + // Index + idx := HeresphereIndex{ + Access: HeresphereMember, + Banner: banner, + Library: []HeresphereIndexEntry{library}, + } + + // Create a JSON encoder for the response writer + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + if err := enc.Encode(idx); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +/* + * This endpoint provides a single scenes full information. + */ +func (rs Routes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { + user := r.Context().Value(authKey).(HeresphereAuthReq) + c := config.GetInstance() + + // Update request + if err := rs.HeresphereVideoDataUpdate(w, r); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Fetch scene + scene := r.Context().Value(sceneKey).(*models.Scene) + + // Load relationships + processedScene := HeresphereVideoEntry{} + if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + return scene.LoadRelationships(ctx, rs.Repository.Scene) + }); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Create scene + processedScene = HeresphereVideoEntry{ + Access: HeresphereMember, + Title: scene.GetTitle(), + Description: scene.Details, + ThumbnailImage: addApiKey(urlbuilders.NewSceneURLBuilder(manager.GetBaseURL(r), scene).GetScreenshotURL()), + ThumbnailVideo: addApiKey(urlbuilders.NewSceneURLBuilder(manager.GetBaseURL(r), scene).GetStreamPreviewURL()), + DateAdded: scene.CreatedAt.Format("2006-01-02"), + Duration: 60000.0, + Rating: 0, + Favorites: 0, + Comments: scene.OCounter, + IsFavorite: getVideoFavorite(rs, r, scene), + Projection: HeresphereProjectionPerspective, + Stereo: HeresphereStereoMono, + IsEyeSwapped: false, + Fov: 180.0, + Lens: HeresphereLensLinear, + CameraIPD: 6.5, + EventServer: addApiKey(fmt.Sprintf("%s/heresphere/%d/event", + manager.GetBaseURL(r), + scene.ID, + )), + Scripts: getVideoScripts(rs, r, scene), + Subtitles: getVideoSubtitles(rs, r, scene), + Tags: getVideoTags(rs, r.Context(), scene), + Media: []HeresphereVideoMedia{}, + WriteFavorite: c.GetHSPWriteFavorites(), + WriteRating: c.GetHSPWriteRatings(), + WriteTags: c.GetHSPWriteTags(), + WriteHSP: false, + } + + // Find projection options + FindProjectionTags(scene, &processedScene) + + // Additional info + if user.NeedsMediaSource != nil && *user.NeedsMediaSource { + processedScene.Media = getVideoMedia(rs, r, scene) + } + if scene.Date != nil { + processedScene.DateReleased = scene.Date.Format("2006-01-02") + } + if scene.Rating != nil { + fiveScale := models.Rating100To5F(*scene.Rating) + processedScene.Rating = fiveScale + } + if processedScene.IsFavorite { + processedScene.Favorites++ + } + if scene.Files.PrimaryLoaded() { + file_ids := scene.Files.Primary() + if file_ids != nil { + processedScene.Duration = manager.HandleFloat64Value(file_ids.Duration * 1000.0) + } + } + + // Create a JSON encoder for the response writer + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + if err := enc.Encode(processedScene); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +/* + * This endpoint function allows the user to login and receive a token if successful. + */ +func (rs Routes) HeresphereLoginToken(w http.ResponseWriter, r *http.Request) { + user := r.Context().Value(authKey).(HeresphereAuthReq) + + // Try login + if basicLogin(user.Username, user.Password) { + writeNotAuthorized(w, r, "Invalid credentials") + return + } + + // Fetch key + key := config.GetInstance().GetAPIKey() + if len(key) == 0 { + writeNotAuthorized(w, r, "Missing auth key!") + return + } + + // Generate auth response + auth := &HeresphereAuthResp{ + AuthToken: key, + Access: HeresphereMember, + } + + // Create a JSON encoder for the response writer + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + if err := enc.Encode(auth); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +/* + * This context function finds the applicable scene from the request and stores it. + */ +func (rs Routes) HeresphereSceneCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Get sceneId + sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) + if err != nil { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + // Resolve scene + var scene *models.Scene + _ = txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + qb := rs.SceneFinder + scene, _ = qb.Find(ctx, sceneID) + + if scene != nil { + // A valid scene should have a attached video + if err := scene.LoadPrimaryFile(ctx, rs.FileFinder); err != nil { + if !errors.Is(err, context.Canceled) { + logger.Errorf("error loading primary file for scene %d: %v", sceneID, err) + } + // set scene to nil so that it doesn't try to use the primary file + scene = nil + } + } + + return nil + }) + if scene == nil { + http.Error(w, http.StatusText(404), 404) + return + } + + ctx := context.WithValue(r.Context(), sceneKey, scene) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +/* + * This context function finds if the authentication is correct, otherwise rejects the request. + */ +func (rs Routes) HeresphereCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Add JSON Header (using Add uses camel case and makes it invalid because "Json") + w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} + + // Only if enabled + if !config.GetInstance().GetHSPDefaultEnabled() { + writeNotAuthorized(w, r, "HereSphere API not enabled!") + return + } + + // Read HTTP Body + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "can't read body", http.StatusBadRequest) + return + } + + // Make the Body re-readable (afaik only /event uses this) + r.Body = io.NopCloser(bytes.NewBuffer(body)) + + // Auth enabled and not has valid credentials (TLDR: needs to be blocked) + isAuth := config.GetInstance().HasCredentials() && !HeresphereHasValidToken(r) + + // Default request + user := HeresphereAuthReq{} + + // Attempt decode, and if err and invalid auth, fail + if err := json.Unmarshal(body, &user); err != nil && isAuth { + writeNotAuthorized(w, r, "Not logged in!") + return + } + + // If empty, fill as true + if user.NeedsMediaSource == nil { + needsMedia := true + user.NeedsMediaSource = &needsMedia + } + + // If invalid creds, only allow auth endpoint + if isAuth && !strings.HasPrefix(r.URL.Path, "/heresphere/auth") { + writeNotAuthorized(w, r, "Unauthorized!") + return + } + + ctx := context.WithValue(r.Context(), authKey, user) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/internal/heresphere/schema.go b/internal/heresphere/schema.go new file mode 100644 index 00000000000..af3d803da89 --- /dev/null +++ b/internal/heresphere/schema.go @@ -0,0 +1,164 @@ +package heresphere + +// Based on HereSphere_JSON_API_Version_1.txt + +const HeresphereJsonVersion = 1 + +const ( + HeresphereGuest = 0 + HeresphereMember = 1 + HeresphereBadLogin = -1 +) + +type HeresphereProjection string + +const ( + HeresphereProjectionEquirectangular HeresphereProjection = "equirectangular" + HeresphereProjectionPerspective HeresphereProjection = "perspective" + HeresphereProjectionEquirectangular360 HeresphereProjection = "equirectangular360" + HeresphereProjectionFisheye HeresphereProjection = "fisheye" + HeresphereProjectionCubemap HeresphereProjection = "cubemap" + HeresphereProjectionEquirectangularCubemap HeresphereProjection = "equiangularCubemap" +) + +type HeresphereStereo string + +const ( + HeresphereStereoMono HeresphereStereo = "mono" + HeresphereStereoSbs HeresphereStereo = "sbs" + HeresphereStereoTB HeresphereStereo = "tb" +) + +type HeresphereLens string + +const ( + HeresphereLensLinear HeresphereLens = "Linear" + HeresphereLensMKX220 HeresphereLens = "MKX220" + HeresphereLensMKX200 HeresphereLens = "MKX200" + HeresphereLensVRCA220 HeresphereLens = "VRCA220" +) + +type HeresphereEventType int + +const ( + HeresphereEventOpen HeresphereEventType = 0 + HeresphereEventPlay HeresphereEventType = 1 + HeresphereEventPause HeresphereEventType = 2 + HeresphereEventClose HeresphereEventType = 3 +) + +const HeresphereAuthHeader = "auth-token" + +type HeresphereAuthResp struct { + AuthToken string `json:"auth-token"` + Access int `json:"access"` +} + +type HeresphereBanner struct { + Image string `json:"image"` + Link string `json:"link"` +} +type HeresphereIndexEntry struct { + Name string `json:"name"` + List []string `json:"list"` +} +type HeresphereIndex struct { + Access int `json:"access"` + Banner HeresphereBanner `json:"banner"` + Library []HeresphereIndexEntry `json:"library"` +} +type HeresphereVideoScript struct { + Name string `json:"name"` + Url string `json:"url"` + Rating float64 `json:"rating,omitempty"` +} +type HeresphereVideoSubtitle struct { + Name string `json:"name"` + Language string `json:"language"` + Url string `json:"url"` +} +type HeresphereVideoTag struct { + Name string `json:"name"` + Start float64 `json:"start,omitempty"` + End float64 `json:"end,omitempty"` + Track int `json:"track,omitempty"` + Rating float64 `json:"rating,omitempty"` +} +type HeresphereVideoMediaSource struct { + Resolution int `json:"resolution"` + Height int `json:"height"` + Width int `json:"width"` + // In bytes + Size int64 `json:"size"` + Url string `json:"url"` +} +type HeresphereVideoMedia struct { + // Media type (h265 etc.) + Name string `json:"name"` + Sources []HeresphereVideoMediaSource `json:"sources"` +} +type HeresphereVideoEntry struct { + Access int `json:"access"` + Title string `json:"title"` + Description string `json:"description"` + ThumbnailImage string `json:"thumbnailImage"` + ThumbnailVideo string `json:"thumbnailVideo,omitempty"` + DateReleased string `json:"dateReleased,omitempty"` + DateAdded string `json:"dateAdded,omitempty"` + Duration float64 `json:"duration,omitempty"` + Rating float64 `json:"rating,omitempty"` + Favorites int `json:"favorites"` + Comments int `json:"comments"` + IsFavorite bool `json:"isFavorite"` + Projection HeresphereProjection `json:"projection"` + Stereo HeresphereStereo `json:"stereo"` + IsEyeSwapped bool `json:"isEyeSwapped"` + Fov float64 `json:"fov,omitempty"` + Lens HeresphereLens `json:"lens"` + CameraIPD float64 `json:"cameraIPD"` + Hsp string `json:"hsp,omitempty"` + EventServer string `json:"eventServer,omitempty"` + Scripts []HeresphereVideoScript `json:"scripts,omitempty"` + Subtitles []HeresphereVideoSubtitle `json:"subtitles,omitempty"` + Tags []HeresphereVideoTag `json:"tags,omitempty"` + Media []HeresphereVideoMedia `json:"media,omitempty"` + WriteFavorite bool `json:"writeFavorite"` + WriteRating bool `json:"writeRating"` + WriteTags bool `json:"writeTags"` + WriteHSP bool `json:"writeHSP"` +} +type HeresphereVideoEntryShort struct { + Link string `json:"link"` + Title string `json:"title"` + DateReleased string `json:"dateReleased,omitempty"` + DateAdded string `json:"dateAdded,omitempty"` + Duration float64 `json:"duration,omitempty"` + Rating float64 `json:"rating,omitempty"` + Favorites int `json:"favorites"` + Comments int `json:"comments"` + IsFavorite bool `json:"isFavorite"` + Tags []HeresphereVideoTag `json:"tags"` +} +type HeresphereScanIndex struct { + ScanData []HeresphereVideoEntryShort `json:"scanData"` +} +type HeresphereAuthReq struct { + Username string `json:"username"` + Password string `json:"password"` + NeedsMediaSource *bool `json:"needsMediaSource,omitempty"` + IsFavorite *bool `json:"isFavorite,omitempty"` + Rating *float64 `json:"rating,omitempty"` + Tags *[]HeresphereVideoTag `json:"tags,omitempty"` + HspBase64 *string `json:"hsp,omitempty"` + DeleteFile *bool `json:"deleteFile,omitempty"` +} +type HeresphereVideoEvent struct { + Username string `json:"username"` + Id string `json:"id"` + Title string `json:"title"` + Event HeresphereEventType `json:"event"` + Time float64 `json:"time"` + Speed float64 `json:"speed"` + Utc float64 `json:"utc"` + ConnectionKey string `json:"connectionKey"` +} diff --git a/internal/heresphere/tags.go b/internal/heresphere/tags.go new file mode 100644 index 00000000000..69184c9cf50 --- /dev/null +++ b/internal/heresphere/tags.go @@ -0,0 +1,429 @@ +package heresphere + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/scene" + "github.com/stashapp/stash/pkg/txn" +) + +/* + * This auxiliary function gathers various tags from the scene to feed the api. + */ +func getVideoTags(rs Routes, ctx context.Context, scene *models.Scene) []HeresphereVideoTag { + processedTags := []HeresphereVideoTag{} + + // Load all relationships + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + return scene.LoadRelationships(ctx, rs.Repository.Scene) + }); err != nil { + return processedTags + } + + txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + mark_ids, err := rs.Repository.SceneMarker.FindBySceneID(ctx, scene.ID) + if err == nil { + for _, mark := range mark_ids { + tagName := mark.Title + + // Add tag name + if ret, err := rs.Repository.Tag.Find(ctx, mark.PrimaryTagID); err == nil { + if len(tagName) == 0 { + tagName = ret.Name + } else { + tagName = fmt.Sprintf("%s - %s", tagName, ret.Name) + } + } + + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Marker:%s", tagName), + Start: mark.Seconds * 1000, + End: (mark.Seconds + 60) * 1000, + } + processedTags = append(processedTags, genTag) + } + + } + + tag_ids, err := rs.Repository.Tag.FindBySceneID(ctx, scene.ID) + if err == nil { + for _, tag := range tag_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Tag:%s", tag.Name), + } + processedTags = append(processedTags, genTag) + } + } + + perf_ids, err := rs.Repository.Performer.FindBySceneID(ctx, scene.ID) + if err == nil { + for _, perf := range perf_ids { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Performer:%s", perf.Name), + } + processedTags = append(processedTags, genTag) + } + } + + if scene.GalleryIDs.Loaded() { + galleries, err := rs.Repository.Gallery.FindMany(ctx, scene.GalleryIDs.List()) + if err == nil { + for _, gallery := range galleries { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Gallery:%s", gallery.Title), + } + processedTags = append(processedTags, genTag) + } + } + } + + if scene.Movies.Loaded() { + lst := scene.Movies.List() + idx := make([]int, 0, len(lst)) + for _, movie := range lst { + idx = append(idx, movie.MovieID) + } + + movies, err := rs.Repository.Movie.FindMany(ctx, idx) + if err == nil { + for _, movie := range movies { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Movie:%s", movie.Name), + } + processedTags = append(processedTags, genTag) + } + } + } + + if scene.StudioID != nil { + studio, _ := rs.Repository.Studio.Find(ctx, *scene.StudioID) + if studio != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Studio:%s", studio.Name), + } + processedTags = append(processedTags, genTag) + } + } + + primaryFile := scene.Files.Primary() + if primaryFile != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagInteractive), + strconv.FormatBool(primaryFile.Interactive), + ), + } + processedTags = append(processedTags, genTag) + + funSpeed := 0 + if primaryFile.InteractiveSpeed != nil { + funSpeed = *primaryFile.InteractiveSpeed + } + genTag = HeresphereVideoTag{ + Name: fmt.Sprintf("Funspeed:%d", + funSpeed, + ), + } + processedTags = append(processedTags, genTag) + } + + return err + }) + + if len(scene.Director) > 0 { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Director:%s", scene.Director), + } + processedTags = append(processedTags, genTag) + } + + if scene.Rating != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Rating:%d", + models.Rating100To5(*scene.Rating), + ), + } + processedTags = append(processedTags, genTag) + } + + { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagWatched), + strconv.FormatBool(scene.PlayCount > 0), + ), + } + processedTags = append(processedTags, genTag) + } + + { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagOrganized), + strconv.FormatBool(scene.Organized), + ), + } + processedTags = append(processedTags, genTag) + } + + { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagRated), + strconv.FormatBool(scene.Rating != nil), + ), + } + processedTags = append(processedTags, genTag) + } + + { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagOrgasmed), + strconv.FormatBool(scene.OCounter > 0), + ), + } + processedTags = append(processedTags, genTag) + } + + { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagPlayCount), scene.PlayCount), + } + processedTags = append(processedTags, genTag) + } + { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagOCounter), scene.OCounter), + } + processedTags = append(processedTags, genTag) + } + + return processedTags +} + +/* + * Processes tags and updates scene tags if applicable + */ +func handleTags(tags *[]HeresphereVideoTag, ctx context.Context, scn *models.Scene, user HeresphereAuthReq, rs Routes, ret *scene.UpdateSet) (bool, error) { + // Search input tags and add/create any new ones + var tagIDs []int + var perfIDs []int + + for _, tagI := range *user.Tags { + // If missing + if len(tagI.Name) == 0 { + continue + } + + // If add tag + // FUTURE IMPROVEMENT: Switch to CutPrefix as it's nicer (1.20+) + // FUTURE IMPROVEMENT: Consider batching searches + if strings.HasPrefix(tagI.Name, "Tag:") { + after := strings.TrimPrefix(tagI.Name, "Tag:") + var err error + var tagMod *models.Tag + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + // Search for tag + tagMod, err = rs.Repository.Tag.FindByName(ctx, after, true) + return err + }); err != nil { + tagMod = nil + } + + if tagMod != nil { + tagIDs = append(tagIDs, tagMod.ID) + } + continue + } + + // If add performer + if strings.HasPrefix(tagI.Name, "Performer:") { + after := strings.TrimPrefix(tagI.Name, "Performer:") + var err error + var tagMod *models.Performer + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + var tagMods []*models.Performer + + // Search for performer + if tagMods, err = rs.Repository.Performer.FindByNames(ctx, []string{after}, true); err == nil && len(tagMods) > 0 { + tagMod = tagMods[0] + } + + return err + }); err != nil { + tagMod = nil + } + + if tagMod != nil { + perfIDs = append(perfIDs, tagMod.ID) + } + continue + } + + // If add marker + if strings.HasPrefix(tagI.Name, "Marker:") { + after := strings.TrimPrefix(tagI.Name, "Marker:") + var tagId *string + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + var err error + var markerResult []*models.MarkerStringsResultType + searchType := "count" + + // Search for marker + if markerResult, err = rs.Repository.SceneMarker.GetMarkerStrings(ctx, &after, &searchType); err == nil && len(markerResult) > 0 { + tagId = &markerResult[0].ID + + // Search for tag + if markers, err := rs.Repository.SceneMarker.FindBySceneID(ctx, scn.ID); err == nil { + i, e := strconv.Atoi(*tagId) + if e == nil { + // Note: Currently we search if a marker exists. + // If it doesn't, create it. + // This also means that markers CANNOT be deleted using the api. + for _, marker := range markers { + if marker.Seconds == tagI.Start && + marker.SceneID == scn.ID && + marker.PrimaryTagID == i { + tagId = nil + } + } + } + } + } + + return err + }); err != nil { + // Create marker + /*if tagId != nil { + newTag := SceneMarkerCreateInput{ + Seconds: tagI.Start, + SceneID: strconv.Itoa(scn.ID), + PrimaryTagID: *tagId, + } + if _, err := rs.resolver.Mutation().SceneMarkerCreate(context.Background(), newTag); err != nil { + return false, err + } + }*/ + } + continue + } + + if strings.HasPrefix(tagI.Name, "Movie:") { + after := strings.TrimPrefix(tagI.Name, "Movie:") + + var err error + var tagMod *models.Movie + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + // Search for performer + tagMod, err = rs.Repository.Movie.FindByName(ctx, after, true) + return err + }); err == nil { + ret.Partial.MovieIDs.Mode = models.RelationshipUpdateModeSet + ret.Partial.MovieIDs.AddUnique(models.MoviesScenes{ + MovieID: tagMod.ID, + SceneIndex: &scn.ID, + }) + } + continue + } + if strings.HasPrefix(tagI.Name, "Studio:") { + after := strings.TrimPrefix(tagI.Name, "Studio:") + + var err error + var tagMod *models.Studio + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + // Search for performer + tagMod, err = rs.Repository.Studio.FindByName(ctx, after, true) + return err + }); err == nil { + ret.Partial.StudioID.Set = true + ret.Partial.StudioID.Value = tagMod.ID + } + continue + } + if strings.HasPrefix(tagI.Name, "Director:") { + after := strings.TrimPrefix(tagI.Name, "Director:") + ret.Partial.Director.Set = true + ret.Partial.Director.Value = after + continue + } + + // Custom + { + tagName := tagI.Name + + // Will be overwritten if PlayCount tag is updated + prefix := string(HeresphereCustomTagWatched) + ":" + if strings.HasPrefix(tagName, prefix) { + after := strings.TrimPrefix(tagName, prefix) + if b, err := strconv.ParseBool(after); err == nil { + // Plays chicken + if b && scn.PlayCount == 0 { + ret.Partial.PlayCount.Set = true + ret.Partial.PlayCount.Value = 1 + } else if !b { + ret.Partial.PlayCount.Set = true + ret.Partial.PlayCount.Value = 0 + } + } + continue + } + prefix = string(HeresphereCustomTagOrganized) + ":" + if strings.HasPrefix(tagName, prefix) { + after := strings.TrimPrefix(tagName, prefix) + if b, err := strconv.ParseBool(after); err == nil { + ret.Partial.Organized.Set = true + ret.Partial.Organized.Value = b + } + continue + } + prefix = string(HeresphereCustomTagRated) + ":" + if strings.HasPrefix(tagName, prefix) { + after := strings.TrimPrefix(tagName, prefix) + if b, err := strconv.ParseBool(after); err == nil && !b { + ret.Partial.Rating.Set = true + ret.Partial.Rating.Null = true + } + continue + } + + // Set numbers + prefix = string(HeresphereCustomTagPlayCount) + ":" + if strings.HasPrefix(tagName, prefix) { + after := strings.TrimPrefix(tagName, prefix) + if numRes, err := strconv.Atoi(after); err != nil { + ret.Partial.PlayCount.Set = true + ret.Partial.PlayCount.Value = numRes + } + continue + } + prefix = string(HeresphereCustomTagOCounter) + ":" + if strings.HasPrefix(tagName, prefix) { + after := strings.TrimPrefix(tagName, prefix) + if numRes, err := strconv.Atoi(after); err != nil { + ret.Partial.OCounter.Set = true + ret.Partial.OCounter.Value = numRes + } + continue + } + } + } + + // Update tags + ret.Partial.TagIDs = &models.UpdateIDs{ + IDs: tagIDs, + Mode: models.RelationshipUpdateModeSet, + } + // Update performers + ret.Partial.PerformerIDs = &models.UpdateIDs{ + IDs: perfIDs, + Mode: models.RelationshipUpdateModeSet, + } + + return true, nil +} diff --git a/internal/heresphere/update.go b/internal/heresphere/update.go new file mode 100644 index 00000000000..36ecef91b02 --- /dev/null +++ b/internal/heresphere/update.go @@ -0,0 +1,70 @@ +package heresphere + +import ( + "context" + + "github.com/stashapp/stash/internal/manager" + "github.com/stashapp/stash/pkg/file" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/scene" + "github.com/stashapp/stash/pkg/txn" +) + +/* + * Modifies the scene rating + */ +func updateRating(user HeresphereAuthReq, ret *scene.UpdateSet) (bool, error) { + rating := models.Rating5To100F(*user.Rating) + ret.Partial.Rating.Value = rating + ret.Partial.Rating.Set = true + return true, nil +} + +/* + * Modifies the scene PlayCount + */ +func updatePlayCount(ctx context.Context, scn *models.Scene, event HeresphereVideoEvent, txnManager txn.Manager, fqb manager.SceneReaderWriter) error { + if per, err := getMinPlayPercent(); err == nil { + newTime := event.Time / 1000 + file := scn.Files.Primary() + + if file != nil && newTime/file.Duration > float64(per)/100.0 && event.Event == HeresphereEventClose { + ret := &scene.UpdateSet{ + ID: scn.ID, + } + ret.Partial = models.NewScenePartial() + ret.Partial.PlayCount.Set = true + ret.Partial.PlayCount.Value = scn.PlayCount + 1 + + if err := txn.WithTxn(ctx, txnManager, func(ctx context.Context) error { + _, err := ret.Update(ctx, fqb) + return err + }); err != nil { + return err + } + } + } + + return nil +} + +/* + * Deletes the scene's primary file + */ +func handleDeletePrimaryFile(ctx context.Context, txnManager txn.Manager, scn *models.Scene, fqb manager.FileReaderWriter, fileDeleter *file.Deleter) (bool, error) { + err := txn.WithTxn(ctx, txnManager, func(ctx context.Context) error { + if err := scn.LoadPrimaryFile(ctx, fqb); err != nil { + return err + } + + ff := scn.Files.Primary() + if ff != nil { + if err := file.Destroy(ctx, fqb, ff, fileDeleter, true); err != nil { + return err + } + } + + return nil + }) + return err == nil, err +} diff --git a/internal/heresphere/utils.go b/internal/heresphere/utils.go new file mode 100644 index 00000000000..d3be21217e2 --- /dev/null +++ b/internal/heresphere/utils.go @@ -0,0 +1,30 @@ +package heresphere + +import ( + "fmt" + + "github.com/stashapp/stash/internal/manager/config" +) + +/* + * Finds the selected VR Tag string + */ +func getVrTag() (varTag string, err error) { + // Find setting + varTag = config.GetInstance().GetUIVRTag() + if len(varTag) == 0 { + err = fmt.Errorf("zero length vr tag") + } + return +} + +/* + * Finds the selected minimum play percentage value + */ +func getMinPlayPercent() (per int, err error) { + per = config.GetInstance().GetUIMinPlayPercent() + if per < 0 { + err = fmt.Errorf("unset minimum play percent") + } + return +} diff --git a/internal/manager/http.go b/internal/manager/http.go new file mode 100644 index 00000000000..4cb13c21833 --- /dev/null +++ b/internal/manager/http.go @@ -0,0 +1,30 @@ +package manager + +import ( + "net/http" + "strings" + + "github.com/stashapp/stash/internal/manager/config" +) + +func GetProxyPrefix(r *http.Request) string { + return strings.TrimRight(r.Header.Get("X-Forwarded-Prefix"), "/") +} + +// Returns stash's baseurl +func GetBaseURL(r *http.Request) string { + scheme := "http" + if strings.Compare("https", r.URL.Scheme) == 0 || r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { + scheme = "https" + } + prefix := GetProxyPrefix(r) + + baseURL := scheme + "://" + r.Host + prefix + + externalHost := config.GetInstance().GetExternalHost() + if externalHost != "" { + baseURL = externalHost + prefix + } + + return baseURL +} diff --git a/internal/manager/json_utils.go b/internal/manager/json_utils.go index c90c9502942..6f68e487aaf 100644 --- a/internal/manager/json_utils.go +++ b/internal/manager/json_utils.go @@ -1,6 +1,7 @@ package manager import ( + "math" "path/filepath" "github.com/stashapp/stash/pkg/models/jsonschema" @@ -42,3 +43,21 @@ func (jp *jsonUtils) saveGallery(fn string, gallery *jsonschema.Gallery) error { func (jp *jsonUtils) saveFile(fn string, file jsonschema.DirEntry) error { return jsonschema.SaveFileFile(filepath.Join(jp.json.Files, fn), file) } + +// #1572 - Inf and NaN values cause the JSON marshaller to fail +// Return nil for these values +func HandleFloat64(v float64) *float64 { + if math.IsInf(v, 0) || math.IsNaN(v) { + return nil + } + + return &v +} + +func HandleFloat64Value(v float64) float64 { + if math.IsInf(v, 0) || math.IsNaN(v) { + return 0 + } + + return v +} diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index 62881d98a6b..d5926cb76d0 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -2035,8 +2035,6 @@ export const useConfigureHSP = () => update: updateConfiguration, }); -export const useEnableHSP = () => GQL.useEnableHspMutation(); - export const mutateReloadScrapers = () => client.mutate({ mutation: GQL.ReloadScrapersDocument, From 0a4b2dff3f25b76c661bfba1fe29f61011ea65bf Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 30 Aug 2023 23:39:04 +0200 Subject: [PATCH 113/144] Bugfixes and logging --- internal/heresphere/auth.go | 2 ++ internal/heresphere/favorite.go | 32 +++++++++++---------- internal/heresphere/keys.go | 2 +- internal/heresphere/media.go | 51 +++++++++++++++++++-------------- internal/heresphere/routes.go | 18 +++++++----- internal/heresphere/tags.go | 51 ++++++++++++++++++++++++--------- 6 files changed, 98 insertions(+), 58 deletions(-) diff --git a/internal/heresphere/auth.go b/internal/heresphere/auth.go index 5a4e0b90d90..45d882676bb 100644 --- a/internal/heresphere/auth.go +++ b/internal/heresphere/auth.go @@ -8,6 +8,7 @@ import ( "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" + "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/session" ) @@ -93,6 +94,7 @@ func writeNotAuthorized(w http.ResponseWriter, r *http.Request, msg string) { enc := json.NewEncoder(w) enc.SetEscapeHTML(false) if err := enc.Encode(idx); err != nil { + logger.Errorf("Heresphere writeNotAuthorized error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } diff --git a/internal/heresphere/favorite.go b/internal/heresphere/favorite.go index f903694ca7b..3582dee33c9 100644 --- a/internal/heresphere/favorite.go +++ b/internal/heresphere/favorite.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/stashapp/stash/internal/manager/config" + "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" "github.com/stashapp/stash/pkg/txn" @@ -33,7 +34,7 @@ func handleFavoriteTag(ctx context.Context, rs Routes, scn *models.Scene, user H } } else { if user.Tags == nil { - sceneTags := getVideoTags(rs, ctx, scn) + sceneTags := getVideoTags(ctx, rs, scn) user.Tags = &sceneTags } @@ -60,21 +61,22 @@ func handleFavoriteTag(ctx context.Context, rs Routes, scn *models.Scene, user H * This auxiliary function searches for the "favorite" tag */ func getVideoFavorite(rs Routes, r *http.Request, scene *models.Scene) bool { - found := false - favTag := config.GetInstance().GetHSPFavoriteTag() + var err error + var tag_ids []*models.Tag + if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + tag_ids, err = rs.Repository.Tag.FindBySceneID(ctx, scene.ID) + return err + }); err != nil { + logger.Errorf("Heresphere getVideoFavorite error: %s\n", err.Error()) + return false + } - txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { - tag_ids, err := rs.Repository.Tag.FindBySceneID(ctx, scene.ID) - if err == nil { - for _, tag := range tag_ids { - if tag.ID == favTag { - found = true - return nil - } - } + favTag := config.GetInstance().GetHSPFavoriteTag() + for _, tag := range tag_ids { + if tag.ID == favTag { + return true } - return err - }) + } - return found + return false } diff --git a/internal/heresphere/keys.go b/internal/heresphere/keys.go index 185a7ad182a..4e3f90f00c7 100644 --- a/internal/heresphere/keys.go +++ b/internal/heresphere/keys.go @@ -3,6 +3,6 @@ package heresphere type key int const ( - sceneKey = iota + 1 + sceneKey key = iota + 1 authKey ) diff --git a/internal/heresphere/media.go b/internal/heresphere/media.go index 18e091e66de..ce83e9e2adc 100644 --- a/internal/heresphere/media.go +++ b/internal/heresphere/media.go @@ -10,6 +10,7 @@ import ( "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/pkg/file" + "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/txn" ) @@ -60,29 +61,32 @@ func getVideoScripts(rs Routes, r *http.Request, scene *models.Scene) []Heresphe func getVideoSubtitles(rs Routes, r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { processedSubtitles := []HeresphereVideoSubtitle{} - txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { - var err error - - primaryFile := scene.Files.Primary() - if primaryFile != nil { - if captions_id, err := rs.Repository.File.GetCaptions(ctx, primaryFile.ID); err == nil { - for _, caption := range captions_id { - processedCaption := HeresphereVideoSubtitle{ - Name: caption.Filename, - Language: caption.LanguageCode, - Url: addApiKey(fmt.Sprintf("%s?lang=%s&type=%s", - urlbuilders.NewSceneURLBuilder(manager.GetBaseURL(r), scene).GetCaptionURL(), - caption.LanguageCode, - caption.CaptionType, - )), - } - processedSubtitles = append(processedSubtitles, processedCaption) - } - } + primaryFile := scene.Files.Primary() + if primaryFile != nil { + var captions_id []*models.VideoCaption + + if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + var err error + captions_id, err = rs.Repository.File.GetCaptions(ctx, primaryFile.ID) + return err + }); err != nil { + logger.Errorf("Heresphere getVideoSubtitles error: %s\n", err.Error()) + return processedSubtitles } - return err - }) + for _, caption := range captions_id { + processedCaption := HeresphereVideoSubtitle{ + Name: caption.Filename, + Language: caption.LanguageCode, + Url: addApiKey(fmt.Sprintf("%s?lang=%s&type=%s", + urlbuilders.NewSceneURLBuilder(manager.GetBaseURL(r), scene).GetCaptionURL(), + caption.LanguageCode, + caption.CaptionType, + )), + } + processedSubtitles = append(processedSubtitles, processedCaption) + } + } return processedSubtitles } @@ -103,7 +107,9 @@ func getTranscodedMediaSources(sceneURL string, transcodeSize int, mediaFile *fi if err != nil { panic(err) } - transcodedUrl.Query().Add("resolution", res.String()) + q := transcodedUrl.Query() + q.Add("resolution", res.String()) + transcodedUrl.RawQuery = q.Encode() processedEntry := HeresphereVideoMediaSource{ Resolution: height, @@ -132,6 +138,7 @@ func getVideoMedia(rs Routes, r *http.Request, scene *models.Scene) []Heresphere if err := txn.WithTxn(r.Context(), rs.Repository.TxnManager, func(ctx context.Context) error { return scene.LoadPrimaryFile(ctx, rs.Repository.File) }); err != nil { + logger.Errorf("Heresphere getVideoMedia error: %s\n", err.Error()) return processedMedia } diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 20eb44cc623..207a4eed9c0 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -18,7 +18,6 @@ import ( "github.com/stashapp/stash/pkg/file" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/plugin" "github.com/stashapp/stash/pkg/scene" "github.com/stashapp/stash/pkg/txn" ) @@ -39,10 +38,6 @@ const ( HeresphereCustomTagRated HeresphereCustomTag = "Rated" ) -type hookExecutor interface { - ExecutePostHooks(ctx context.Context, id int, hookType plugin.HookTriggerEnum, input interface{}, inputFields []string) -} - type Routes struct { TxnManager txn.Manager SceneFinder sceneFinder @@ -88,6 +83,7 @@ func (rs Routes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { var event HeresphereVideoEvent err := json.NewDecoder(r.Body).Decode(&event) if err != nil { + logger.Errorf("Heresphere HeresphereVideoEvent decode error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -100,6 +96,7 @@ func (rs Routes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { } if err := updatePlayCount(r.Context(), scn, event, rs.TxnManager, rs.Repository.Scene); err != nil { + logger.Errorf("Heresphere HeresphereVideoEvent updatePlayCount error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -108,6 +105,7 @@ func (rs Routes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { _, err := rs.Repository.Scene.SaveActivity(ctx, scn.ID, &newTime, &newDuration) return err }); err != nil { + logger.Errorf("Heresphere HeresphereVideoEvent SaveActivity error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -156,7 +154,7 @@ func (rs Routes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Reques } if user.Tags != nil && c.GetHSPWriteTags() { - if b, err = handleTags(user.Tags, r.Context(), scn, user, rs, ret); err != nil { + if b, err = handleTags(r.Context(), user.Tags, scn, user, rs, ret); err != nil { return err } shouldUpdate = b || shouldUpdate @@ -199,6 +197,7 @@ func (rs Routes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { scenes, err = rs.Repository.Scene.All(ctx) return err }); err != nil { + logger.Errorf("Heresphere HeresphereIndex SceneAll error: %s\n", err.Error()) http.Error(w, "Failed to fetch scenes!", http.StatusInternalServerError) return } @@ -229,6 +228,7 @@ func (rs Routes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.SetEscapeHTML(false) if err := enc.Encode(idx); err != nil { + logger.Errorf("Heresphere HeresphereIndex encode error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -243,6 +243,7 @@ func (rs Routes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { // Update request if err := rs.HeresphereVideoDataUpdate(w, r); err != nil { + logger.Errorf("Heresphere HeresphereVideoData HeresphereVideoDataUpdate error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -255,6 +256,7 @@ func (rs Routes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { return scene.LoadRelationships(ctx, rs.Repository.Scene) }); err != nil { + logger.Errorf("Heresphere HeresphereVideoData LoadRelationships error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -284,7 +286,7 @@ func (rs Routes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { )), Scripts: getVideoScripts(rs, r, scene), Subtitles: getVideoSubtitles(rs, r, scene), - Tags: getVideoTags(rs, r.Context(), scene), + Tags: getVideoTags(r.Context(), rs, scene), Media: []HeresphereVideoMedia{}, WriteFavorite: c.GetHSPWriteFavorites(), WriteRating: c.GetHSPWriteRatings(), @@ -321,6 +323,7 @@ func (rs Routes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.SetEscapeHTML(false) if err := enc.Encode(processedScene); err != nil { + logger.Errorf("Heresphere HeresphereVideoData encode error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -356,6 +359,7 @@ func (rs Routes) HeresphereLoginToken(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.SetEscapeHTML(false) if err := enc.Encode(auth); err != nil { + logger.Errorf("Heresphere HeresphereLoginToken encode error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } diff --git a/internal/heresphere/tags.go b/internal/heresphere/tags.go index 69184c9cf50..478a25fab25 100644 --- a/internal/heresphere/tags.go +++ b/internal/heresphere/tags.go @@ -5,7 +5,9 @@ import ( "fmt" "strconv" "strings" + "time" + "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" "github.com/stashapp/stash/pkg/txn" @@ -14,17 +16,18 @@ import ( /* * This auxiliary function gathers various tags from the scene to feed the api. */ -func getVideoTags(rs Routes, ctx context.Context, scene *models.Scene) []HeresphereVideoTag { +func getVideoTags(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { processedTags := []HeresphereVideoTag{} // Load all relationships if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { return scene.LoadRelationships(ctx, rs.Repository.Scene) }); err != nil { + logger.Errorf("Heresphere getVideoTags LoadRelationships error: %s\n", err.Error()) return processedTags } - txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { mark_ids, err := rs.Repository.SceneMarker.FindBySceneID(ctx, scene.ID) if err == nil { for _, mark := range mark_ids { @@ -46,7 +49,8 @@ func getVideoTags(rs Routes, ctx context.Context, scene *models.Scene) []Heresph } processedTags = append(processedTags, genTag) } - + } else { + logger.Errorf("Heresphere getVideoTags SceneMarker.FindBySceneID error: %s\n", err.Error()) } tag_ids, err := rs.Repository.Tag.FindBySceneID(ctx, scene.ID) @@ -57,6 +61,8 @@ func getVideoTags(rs Routes, ctx context.Context, scene *models.Scene) []Heresph } processedTags = append(processedTags, genTag) } + } else { + logger.Errorf("Heresphere getVideoTags Tag.FindBySceneID error: %s\n", err.Error()) } perf_ids, err := rs.Repository.Performer.FindBySceneID(ctx, scene.ID) @@ -67,6 +73,8 @@ func getVideoTags(rs Routes, ctx context.Context, scene *models.Scene) []Heresph } processedTags = append(processedTags, genTag) } + } else { + logger.Errorf("Heresphere getVideoTags Performer.FindBySceneID error: %s\n", err.Error()) } if scene.GalleryIDs.Loaded() { @@ -78,6 +86,8 @@ func getVideoTags(rs Routes, ctx context.Context, scene *models.Scene) []Heresph } processedTags = append(processedTags, genTag) } + } else { + logger.Errorf("Heresphere getVideoTags Gallery.FindMany error: %s\n", err.Error()) } } @@ -96,17 +106,22 @@ func getVideoTags(rs Routes, ctx context.Context, scene *models.Scene) []Heresph } processedTags = append(processedTags, genTag) } + } else { + logger.Errorf("Heresphere getVideoTags Movie.FindMany error: %s\n", err.Error()) } } if scene.StudioID != nil { - studio, _ := rs.Repository.Studio.Find(ctx, *scene.StudioID) + studio, err := rs.Repository.Studio.Find(ctx, *scene.StudioID) if studio != nil { genTag := HeresphereVideoTag{ Name: fmt.Sprintf("Studio:%s", studio.Name), } processedTags = append(processedTags, genTag) } + if err != nil { + logger.Errorf("Heresphere getVideoTags Studio.Find error: %s\n", err.Error()) + } } primaryFile := scene.Files.Primary() @@ -132,7 +147,9 @@ func getVideoTags(rs Routes, ctx context.Context, scene *models.Scene) []Heresph } return err - }) + }); err != nil { + fmt.Printf("Heresphere getVideoTags scene reader error: %s\n", err.Error()) + } if len(scene.Director) > 0 { genTag := HeresphereVideoTag{ @@ -209,7 +226,7 @@ func getVideoTags(rs Routes, ctx context.Context, scene *models.Scene) []Heresph /* * Processes tags and updates scene tags if applicable */ -func handleTags(tags *[]HeresphereVideoTag, ctx context.Context, scn *models.Scene, user HeresphereAuthReq, rs Routes, ret *scene.UpdateSet) (bool, error) { +func handleTags(ctx context.Context, tags *[]HeresphereVideoTag, scn *models.Scene, user HeresphereAuthReq, rs Routes, ret *scene.UpdateSet) (bool, error) { // Search input tags and add/create any new ones var tagIDs []int var perfIDs []int @@ -232,6 +249,7 @@ func handleTags(tags *[]HeresphereVideoTag, ctx context.Context, scn *models.Sce tagMod, err = rs.Repository.Tag.FindByName(ctx, after, true) return err }); err != nil { + fmt.Printf("Heresphere handleTags Tag.FindByName error: %s\n", err.Error()) tagMod = nil } @@ -256,6 +274,7 @@ func handleTags(tags *[]HeresphereVideoTag, ctx context.Context, scn *models.Sce return err }); err != nil { + fmt.Printf("Heresphere handleTags Performer.FindByNames error: %s\n", err.Error()) tagMod = nil } @@ -299,16 +318,22 @@ func handleTags(tags *[]HeresphereVideoTag, ctx context.Context, scn *models.Sce return err }); err != nil { // Create marker - /*if tagId != nil { - newTag := SceneMarkerCreateInput{ + i, e := strconv.Atoi(*tagId) + if tagId != nil && e == nil { + currentTime := time.Now() + newMarker := models.SceneMarker{ + Title: "", Seconds: tagI.Start, - SceneID: strconv.Itoa(scn.ID), - PrimaryTagID: *tagId, + PrimaryTagID: i, + SceneID: scn.ID, + CreatedAt: currentTime, + UpdatedAt: currentTime, } - if _, err := rs.resolver.Mutation().SceneMarkerCreate(context.Background(), newTag); err != nil { - return false, err + + if rs.Repository.SceneMarker.Create(ctx, &newMarker) != nil { + logger.Errorf("Heresphere handleTags SceneMarker.Create error: %s\n", err.Error()) } - }*/ + } } continue } From 2f018fc967c461a3dd2dc133f6887b98ad70b38b Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 30 Aug 2023 23:42:45 +0200 Subject: [PATCH 114/144] Moved playback event check --- internal/heresphere/routes.go | 37 ++++++++++++++++++----------------- internal/heresphere/update.go | 2 +- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 207a4eed9c0..eaee9e678c3 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -88,26 +88,27 @@ func (rs Routes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { return } - // This is kinda sketchy, since we dont know if the user skipped ahead - newTime := event.Time / 1000 - newDuration := 0.0 - if newTime > scn.ResumeTime { - newDuration += (newTime - scn.ResumeTime) - } + if event.Event == HeresphereEventClose { + newTime := event.Time / 1000 + newDuration := 0.0 + if newTime > scn.ResumeTime { + newDuration += (newTime - scn.ResumeTime) + } - if err := updatePlayCount(r.Context(), scn, event, rs.TxnManager, rs.Repository.Scene); err != nil { - logger.Errorf("Heresphere HeresphereVideoEvent updatePlayCount error: %s\n", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + if err := updatePlayCount(r.Context(), scn, event, rs.TxnManager, rs.Repository.Scene); err != nil { + logger.Errorf("Heresphere HeresphereVideoEvent updatePlayCount error: %s\n", err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } - if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { - _, err := rs.Repository.Scene.SaveActivity(ctx, scn.ID, &newTime, &newDuration) - return err - }); err != nil { - logger.Errorf("Heresphere HeresphereVideoEvent SaveActivity error: %s\n", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + _, err := rs.Repository.Scene.SaveActivity(ctx, scn.ID, &newTime, &newDuration) + return err + }); err != nil { + logger.Errorf("Heresphere HeresphereVideoEvent SaveActivity error: %s\n", err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } w.WriteHeader(http.StatusOK) diff --git a/internal/heresphere/update.go b/internal/heresphere/update.go index 36ecef91b02..688960d48c5 100644 --- a/internal/heresphere/update.go +++ b/internal/heresphere/update.go @@ -28,7 +28,7 @@ func updatePlayCount(ctx context.Context, scn *models.Scene, event HeresphereVid newTime := event.Time / 1000 file := scn.Files.Primary() - if file != nil && newTime/file.Duration > float64(per)/100.0 && event.Event == HeresphereEventClose { + if file != nil && newTime/file.Duration > float64(per)/100.0 { ret := &scene.UpdateSet{ ID: scn.ID, } From de77ddede2953782cbac133568878acae22fbc7a Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 30 Aug 2023 23:44:39 +0200 Subject: [PATCH 115/144] Only add funspeed if interactive --- internal/heresphere/tags.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/heresphere/tags.go b/internal/heresphere/tags.go index 478a25fab25..eeb9a39622b 100644 --- a/internal/heresphere/tags.go +++ b/internal/heresphere/tags.go @@ -134,16 +134,18 @@ func getVideoTags(ctx context.Context, rs Routes, scene *models.Scene) []Heresph } processedTags = append(processedTags, genTag) - funSpeed := 0 - if primaryFile.InteractiveSpeed != nil { - funSpeed = *primaryFile.InteractiveSpeed - } - genTag = HeresphereVideoTag{ - Name: fmt.Sprintf("Funspeed:%d", - funSpeed, - ), + if primaryFile.Interactive { + funSpeed := 0 + if primaryFile.InteractiveSpeed != nil { + funSpeed = *primaryFile.InteractiveSpeed + } + genTag = HeresphereVideoTag{ + Name: fmt.Sprintf("Funspeed:%d", + funSpeed, + ), + } + processedTags = append(processedTags, genTag) } - processedTags = append(processedTags, genTag) } return err From b4cdf3e7b62ed8144a33e5c64c2e387e3d328a91 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:27:36 +0200 Subject: [PATCH 116/144] Fix missing pointer for tag updating --- internal/heresphere/favorite.go | 58 ++++++++++++++++----------------- internal/heresphere/routes.go | 4 +-- internal/heresphere/tags.go | 2 +- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/internal/heresphere/favorite.go b/internal/heresphere/favorite.go index 3582dee33c9..5135f3a1d83 100644 --- a/internal/heresphere/favorite.go +++ b/internal/heresphere/favorite.go @@ -16,42 +16,40 @@ import ( * Searches for favorite tag if it exists, otherwise adds it. * This adds a tag, which means tags must also be enabled, or it will never be written. */ -func handleFavoriteTag(ctx context.Context, rs Routes, scn *models.Scene, user HeresphereAuthReq, txnManager txn.Manager, ret *scene.UpdateSet) (bool, error) { +func handleFavoriteTag(ctx context.Context, rs Routes, scn *models.Scene, user *HeresphereAuthReq, txnManager txn.Manager, ret *scene.UpdateSet) (bool, error) { + var err error + var favTag *models.Tag + tagId := config.GetInstance().GetHSPFavoriteTag() + if err := txn.WithReadTxn(ctx, txnManager, func(ctx context.Context) error { - var err error - var favTag *models.Tag + favTag, err = rs.Repository.Tag.Find(ctx, tagId) + return err + }); err != nil { + logger.Errorf("Heresphere handleFavoriteTag Tag.Find error: %s\n", err.Error()) + return false, err + } - tagId := config.GetInstance().GetHSPFavoriteTag() - if favTag, err = rs.Repository.Tag.Find(ctx, tagId); err == nil { - favTagVal := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%s", favTag.Name)} + favTagVal := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%s", favTag.Name)} - // Do the old switcheroo to figure out how to add the tag - if *user.IsFavorite { - if user.Tags == nil { - user.Tags = &[]HeresphereVideoTag{favTagVal} - } else { - *user.Tags = append(*user.Tags, favTagVal) - } - } else { - if user.Tags == nil { - sceneTags := getVideoTags(ctx, rs, scn) - user.Tags = &sceneTags - } + // Do the old switcheroo to figure out how to add the tag + if *user.IsFavorite { + if user.Tags == nil { + user.Tags = &[]HeresphereVideoTag{favTagVal} + } else { + *user.Tags = append(*user.Tags, favTagVal) + } + } else { + if user.Tags == nil { + sceneTags := getVideoTags(ctx, rs, scn) + user.Tags = &sceneTags + } - for i, tag := range *user.Tags { - if tag.Name == favTagVal.Name { - *user.Tags = append((*user.Tags)[:i], (*user.Tags)[i+1:]...) - break - } - } + for i, tag := range *user.Tags { + if tag.Name == favTagVal.Name { + *user.Tags = append((*user.Tags)[:i], (*user.Tags)[i+1:]...) + break } - - return nil } - - return err - }); err != nil { - return false, err } return true, nil diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index eaee9e678c3..36b1efbad81 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -148,14 +148,14 @@ func (rs Routes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Reques } if user.IsFavorite != nil && c.GetHSPWriteFavorites() { - if b, err = handleFavoriteTag(r.Context(), rs, scn, user, rs.TxnManager, ret); err != nil { + if b, err = handleFavoriteTag(r.Context(), rs, scn, &user, rs.TxnManager, ret); err != nil { return err } shouldUpdate = b || shouldUpdate } if user.Tags != nil && c.GetHSPWriteTags() { - if b, err = handleTags(r.Context(), user.Tags, scn, user, rs, ret); err != nil { + if b, err = handleTags(r.Context(), scn, &user, rs, ret); err != nil { return err } shouldUpdate = b || shouldUpdate diff --git a/internal/heresphere/tags.go b/internal/heresphere/tags.go index eeb9a39622b..05f7330d778 100644 --- a/internal/heresphere/tags.go +++ b/internal/heresphere/tags.go @@ -228,7 +228,7 @@ func getVideoTags(ctx context.Context, rs Routes, scene *models.Scene) []Heresph /* * Processes tags and updates scene tags if applicable */ -func handleTags(ctx context.Context, tags *[]HeresphereVideoTag, scn *models.Scene, user HeresphereAuthReq, rs Routes, ret *scene.UpdateSet) (bool, error) { +func handleTags(ctx context.Context, scn *models.Scene, user *HeresphereAuthReq, rs Routes, ret *scene.UpdateSet) (bool, error) { // Search input tags and add/create any new ones var tagIDs []int var perfIDs []int From 20829096a639d6731f318365fa3b5ff2552b8192 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 31 Aug 2023 16:20:45 +0200 Subject: [PATCH 117/144] 2nd round of refactors --- internal/heresphere/favorite.go | 43 ++-- internal/heresphere/media.go | 20 +- internal/heresphere/routes.go | 36 ++- internal/heresphere/tags.go | 423 ++++++++++++++++++-------------- 4 files changed, 296 insertions(+), 226 deletions(-) diff --git a/internal/heresphere/favorite.go b/internal/heresphere/favorite.go index 5135f3a1d83..a48b926bc8c 100644 --- a/internal/heresphere/favorite.go +++ b/internal/heresphere/favorite.go @@ -17,21 +17,29 @@ import ( * This adds a tag, which means tags must also be enabled, or it will never be written. */ func handleFavoriteTag(ctx context.Context, rs Routes, scn *models.Scene, user *HeresphereAuthReq, txnManager txn.Manager, ret *scene.UpdateSet) (bool, error) { - var err error - var favTag *models.Tag - tagId := config.GetInstance().GetHSPFavoriteTag() + tagID := config.GetInstance().GetHSPFavoriteTag() - if err := txn.WithReadTxn(ctx, txnManager, func(ctx context.Context) error { - favTag, err = rs.Repository.Tag.Find(ctx, tagId) - return err - }); err != nil { + favTag, err := func() (*models.Tag, error) { + var tag *models.Tag + var err error + err = txn.WithReadTxn(ctx, txnManager, func(ctx context.Context) error { + tag, err = rs.Repository.Tag.Find(ctx, tagID) + return err + }) + return tag, err + }() + + if err != nil { logger.Errorf("Heresphere handleFavoriteTag Tag.Find error: %s\n", err.Error()) return false, err } + if favTag == nil { + return false, nil + } + favTagVal := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%s", favTag.Name)} - // Do the old switcheroo to figure out how to add the tag if *user.IsFavorite { if user.Tags == nil { user.Tags = &[]HeresphereVideoTag{favTagVal} @@ -59,18 +67,23 @@ func handleFavoriteTag(ctx context.Context, rs Routes, scn *models.Scene, user * * This auxiliary function searches for the "favorite" tag */ func getVideoFavorite(rs Routes, r *http.Request, scene *models.Scene) bool { - var err error - var tag_ids []*models.Tag - if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { - tag_ids, err = rs.Repository.Tag.FindBySceneID(ctx, scene.ID) - return err - }); err != nil { + tagIDs, err := func() ([]*models.Tag, error) { + var tags []*models.Tag + var err error + err = txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + tags, err = rs.Repository.Tag.FindBySceneID(ctx, scene.ID) + return err + }) + return tags, err + }() + + if err != nil { logger.Errorf("Heresphere getVideoFavorite error: %s\n", err.Error()) return false } favTag := config.GetInstance().GetHSPFavoriteTag() - for _, tag := range tag_ids { + for _, tag := range tagIDs { if tag.ID == favTag { return true } diff --git a/internal/heresphere/media.go b/internal/heresphere/media.go index ce83e9e2adc..c97a83d2362 100644 --- a/internal/heresphere/media.go +++ b/internal/heresphere/media.go @@ -59,22 +59,26 @@ func getVideoScripts(rs Routes, r *http.Request, scene *models.Scene) []Heresphe * This auxiliary function gathers subtitles if applicable */ func getVideoSubtitles(rs Routes, r *http.Request, scene *models.Scene) []HeresphereVideoSubtitle { - processedSubtitles := []HeresphereVideoSubtitle{} + processedSubtitles := make([]HeresphereVideoSubtitle, 0) primaryFile := scene.Files.Primary() if primaryFile != nil { - var captions_id []*models.VideoCaption - - if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + captions, err := func() ([]*models.VideoCaption, error) { + var captions []*models.VideoCaption var err error - captions_id, err = rs.Repository.File.GetCaptions(ctx, primaryFile.ID) - return err - }); err != nil { + err = txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + captions, err = rs.Repository.File.GetCaptions(ctx, primaryFile.ID) + return err + }) + return captions, err + }() + + if err != nil { logger.Errorf("Heresphere getVideoSubtitles error: %s\n", err.Error()) return processedSubtitles } - for _, caption := range captions_id { + for _, caption := range captions { processedCaption := HeresphereVideoSubtitle{ Name: caption.Filename, Language: caption.LanguageCode, diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 36b1efbad81..b0bbd4619ee 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -181,35 +181,31 @@ func (rs Routes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Reques func (rs Routes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { // Banner banner := HeresphereBanner{ - Image: fmt.Sprintf("%s%s", - manager.GetBaseURL(r), - "/apple-touch-icon.png", - ), - Link: fmt.Sprintf("%s%s", - manager.GetBaseURL(r), - "/", - ), + Image: fmt.Sprintf("%s%s", manager.GetBaseURL(r), "/apple-touch-icon.png"), + Link: fmt.Sprintf("%s%s", manager.GetBaseURL(r), "/"), } // Read scenes - var scenes []*models.Scene - if err := txn.WithReadTxn(r.Context(), rs.Repository.TxnManager, func(ctx context.Context) error { - var err error - scenes, err = rs.Repository.Scene.All(ctx) - return err - }); err != nil { + scenes, err := func() ([]*models.Scene, error) { + var scenes []*models.Scene + err := txn.WithReadTxn(r.Context(), rs.Repository.TxnManager, func(ctx context.Context) error { + var err error + scenes, err = rs.Repository.Scene.All(ctx) + return err + }) + return scenes, err + }() + + if err != nil { logger.Errorf("Heresphere HeresphereIndex SceneAll error: %s\n", err.Error()) http.Error(w, "Failed to fetch scenes!", http.StatusInternalServerError) return } - // Create scene list + // Create scene URLs sceneUrls := make([]string, len(scenes)) for idx, scene := range scenes { - sceneUrls[idx] = fmt.Sprintf("%s/heresphere/%d", - manager.GetBaseURL(r), - scene.ID, - ) + sceneUrls[idx] = fmt.Sprintf("%s/heresphere/%d", manager.GetBaseURL(r), scene.ID) } // All library @@ -224,7 +220,7 @@ func (rs Routes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { Library: []HeresphereIndexEntry{library}, } - // Create a JSON encoder for the response writer + // Set response headers and encode JSON w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) enc.SetEscapeHTML(false) diff --git a/internal/heresphere/tags.go b/internal/heresphere/tags.go index 05f7330d778..896349fabd9 100644 --- a/internal/heresphere/tags.go +++ b/internal/heresphere/tags.go @@ -239,205 +239,42 @@ func handleTags(ctx context.Context, scn *models.Scene, user *HeresphereAuthReq, continue } - // If add tag // FUTURE IMPROVEMENT: Switch to CutPrefix as it's nicer (1.20+) // FUTURE IMPROVEMENT: Consider batching searches - if strings.HasPrefix(tagI.Name, "Tag:") { - after := strings.TrimPrefix(tagI.Name, "Tag:") - var err error - var tagMod *models.Tag - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - // Search for tag - tagMod, err = rs.Repository.Tag.FindByName(ctx, after, true) - return err - }); err != nil { - fmt.Printf("Heresphere handleTags Tag.FindByName error: %s\n", err.Error()) - tagMod = nil - } - - if tagMod != nil { - tagIDs = append(tagIDs, tagMod.ID) - } + if handleAddTag(ctx, rs, tagI, &tagIDs) { continue } - - // If add performer - if strings.HasPrefix(tagI.Name, "Performer:") { - after := strings.TrimPrefix(tagI.Name, "Performer:") - var err error - var tagMod *models.Performer - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - var tagMods []*models.Performer - - // Search for performer - if tagMods, err = rs.Repository.Performer.FindByNames(ctx, []string{after}, true); err == nil && len(tagMods) > 0 { - tagMod = tagMods[0] - } - - return err - }); err != nil { - fmt.Printf("Heresphere handleTags Performer.FindByNames error: %s\n", err.Error()) - tagMod = nil - } - - if tagMod != nil { - perfIDs = append(perfIDs, tagMod.ID) - } + if handleAddPerformer(ctx, rs, tagI, &perfIDs) { continue } - - // If add marker - if strings.HasPrefix(tagI.Name, "Marker:") { - after := strings.TrimPrefix(tagI.Name, "Marker:") - var tagId *string - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - var err error - var markerResult []*models.MarkerStringsResultType - searchType := "count" - - // Search for marker - if markerResult, err = rs.Repository.SceneMarker.GetMarkerStrings(ctx, &after, &searchType); err == nil && len(markerResult) > 0 { - tagId = &markerResult[0].ID - - // Search for tag - if markers, err := rs.Repository.SceneMarker.FindBySceneID(ctx, scn.ID); err == nil { - i, e := strconv.Atoi(*tagId) - if e == nil { - // Note: Currently we search if a marker exists. - // If it doesn't, create it. - // This also means that markers CANNOT be deleted using the api. - for _, marker := range markers { - if marker.Seconds == tagI.Start && - marker.SceneID == scn.ID && - marker.PrimaryTagID == i { - tagId = nil - } - } - } - } - } - - return err - }); err != nil { - // Create marker - i, e := strconv.Atoi(*tagId) - if tagId != nil && e == nil { - currentTime := time.Now() - newMarker := models.SceneMarker{ - Title: "", - Seconds: tagI.Start, - PrimaryTagID: i, - SceneID: scn.ID, - CreatedAt: currentTime, - UpdatedAt: currentTime, - } - - if rs.Repository.SceneMarker.Create(ctx, &newMarker) != nil { - logger.Errorf("Heresphere handleTags SceneMarker.Create error: %s\n", err.Error()) - } - } - } + if handleAddMarker(ctx, rs, tagI, scn) { continue } - - if strings.HasPrefix(tagI.Name, "Movie:") { - after := strings.TrimPrefix(tagI.Name, "Movie:") - - var err error - var tagMod *models.Movie - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - // Search for performer - tagMod, err = rs.Repository.Movie.FindByName(ctx, after, true) - return err - }); err == nil { - ret.Partial.MovieIDs.Mode = models.RelationshipUpdateModeSet - ret.Partial.MovieIDs.AddUnique(models.MoviesScenes{ - MovieID: tagMod.ID, - SceneIndex: &scn.ID, - }) - } + if handleAddMovie(ctx, rs, tagI, scn, ret) { continue } - if strings.HasPrefix(tagI.Name, "Studio:") { - after := strings.TrimPrefix(tagI.Name, "Studio:") - - var err error - var tagMod *models.Studio - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - // Search for performer - tagMod, err = rs.Repository.Studio.FindByName(ctx, after, true) - return err - }); err == nil { - ret.Partial.StudioID.Set = true - ret.Partial.StudioID.Value = tagMod.ID - } + if handleAddStudio(ctx, rs, tagI, scn, ret) { continue } - if strings.HasPrefix(tagI.Name, "Director:") { - after := strings.TrimPrefix(tagI.Name, "Director:") - ret.Partial.Director.Set = true - ret.Partial.Director.Value = after + if handleAddDirector(ctx, rs, tagI, scn, ret) { continue } // Custom - { - tagName := tagI.Name - - // Will be overwritten if PlayCount tag is updated - prefix := string(HeresphereCustomTagWatched) + ":" - if strings.HasPrefix(tagName, prefix) { - after := strings.TrimPrefix(tagName, prefix) - if b, err := strconv.ParseBool(after); err == nil { - // Plays chicken - if b && scn.PlayCount == 0 { - ret.Partial.PlayCount.Set = true - ret.Partial.PlayCount.Value = 1 - } else if !b { - ret.Partial.PlayCount.Set = true - ret.Partial.PlayCount.Value = 0 - } - } - continue - } - prefix = string(HeresphereCustomTagOrganized) + ":" - if strings.HasPrefix(tagName, prefix) { - after := strings.TrimPrefix(tagName, prefix) - if b, err := strconv.ParseBool(after); err == nil { - ret.Partial.Organized.Set = true - ret.Partial.Organized.Value = b - } - continue - } - prefix = string(HeresphereCustomTagRated) + ":" - if strings.HasPrefix(tagName, prefix) { - after := strings.TrimPrefix(tagName, prefix) - if b, err := strconv.ParseBool(after); err == nil && !b { - ret.Partial.Rating.Set = true - ret.Partial.Rating.Null = true - } - continue - } - - // Set numbers - prefix = string(HeresphereCustomTagPlayCount) + ":" - if strings.HasPrefix(tagName, prefix) { - after := strings.TrimPrefix(tagName, prefix) - if numRes, err := strconv.Atoi(after); err != nil { - ret.Partial.PlayCount.Set = true - ret.Partial.PlayCount.Value = numRes - } - continue - } - prefix = string(HeresphereCustomTagOCounter) + ":" - if strings.HasPrefix(tagName, prefix) { - after := strings.TrimPrefix(tagName, prefix) - if numRes, err := strconv.Atoi(after); err != nil { - ret.Partial.OCounter.Set = true - ret.Partial.OCounter.Value = numRes - } - continue - } + if handleSetWatched(ctx, rs, tagI, scn, ret) { + continue + } + if handleSetOrganized(ctx, rs, tagI, scn, ret) { + continue + } + if handleSetRated(ctx, rs, tagI, scn, ret) { + continue + } + if handleSetPlayCount(ctx, rs, tagI, scn, ret) { + continue + } + if handleSetOCount(ctx, rs, tagI, scn, ret) { + continue } } @@ -454,3 +291,223 @@ func handleTags(ctx context.Context, scn *models.Scene, user *HeresphereAuthReq, return true, nil } + +func handleAddTag(ctx context.Context, rs Routes, tag HeresphereVideoTag, tagIDs *[]int) bool { + if strings.HasPrefix(tag.Name, "Tag:") { + after := strings.TrimPrefix(tag.Name, "Tag:") + var err error + var tagMod *models.Tag + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + // Search for tag + tagMod, err = rs.Repository.Tag.FindByName(ctx, after, true) + return err + }); err != nil { + fmt.Printf("Heresphere handleTags Tag.FindByName error: %s\n", err.Error()) + tagMod = nil + } + + if tagMod != nil { + *tagIDs = append(*tagIDs, tagMod.ID) + } + return true + } + return false +} +func handleAddPerformer(ctx context.Context, rs Routes, tag HeresphereVideoTag, perfIDs *[]int) bool { + if strings.HasPrefix(tag.Name, "Performer:") { + after := strings.TrimPrefix(tag.Name, "Performer:") + var err error + var tagMod *models.Performer + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + var tagMods []*models.Performer + + // Search for performer + if tagMods, err = rs.Repository.Performer.FindByNames(ctx, []string{after}, true); err == nil && len(tagMods) > 0 { + tagMod = tagMods[0] + } + + return err + }); err != nil { + fmt.Printf("Heresphere handleTags Performer.FindByNames error: %s\n", err.Error()) + tagMod = nil + } + + if tagMod != nil { + *perfIDs = append(*perfIDs, tagMod.ID) + } + return true + } + return false +} +func handleAddMarker(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene) bool { + if strings.HasPrefix(tag.Name, "Marker:") { + after := strings.TrimPrefix(tag.Name, "Marker:") + var tagId *string + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + var err error + var markerResult []*models.MarkerStringsResultType + searchType := "count" + + // Search for marker + if markerResult, err = rs.Repository.SceneMarker.GetMarkerStrings(ctx, &after, &searchType); err == nil && len(markerResult) > 0 { + tagId = &markerResult[0].ID + + // Search for tag + if markers, err := rs.Repository.SceneMarker.FindBySceneID(ctx, scene.ID); err == nil { + i, e := strconv.Atoi(*tagId) + if e == nil { + // Note: Currently we search if a marker exists. + // If it doesn't, create it. + // This also means that markers CANNOT be deleted using the api. + for _, marker := range markers { + if marker.Seconds == tag.Start && + marker.SceneID == scene.ID && + marker.PrimaryTagID == i { + tagId = nil + } + } + } + } + } + + return err + }); err != nil { + // Create marker + i, e := strconv.Atoi(*tagId) + if tagId != nil && e == nil { + currentTime := time.Now() + newMarker := models.SceneMarker{ + Title: "", + Seconds: tag.Start, + PrimaryTagID: i, + SceneID: scene.ID, + CreatedAt: currentTime, + UpdatedAt: currentTime, + } + + if rs.Repository.SceneMarker.Create(ctx, &newMarker) != nil { + logger.Errorf("Heresphere handleTags SceneMarker.Create error: %s\n", err.Error()) + } + } + } + return true + } + return false +} +func handleAddMovie(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { + if strings.HasPrefix(tag.Name, "Movie:") { + after := strings.TrimPrefix(tag.Name, "Movie:") + + var err error + var tagMod *models.Movie + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + // Search for performer + tagMod, err = rs.Repository.Movie.FindByName(ctx, after, true) + return err + }); err == nil { + ret.Partial.MovieIDs.Mode = models.RelationshipUpdateModeSet + ret.Partial.MovieIDs.AddUnique(models.MoviesScenes{ + MovieID: tagMod.ID, + SceneIndex: &scene.ID, + }) + } + return true + } + return false +} +func handleAddStudio(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { + if strings.HasPrefix(tag.Name, "Studio:") { + after := strings.TrimPrefix(tag.Name, "Studio:") + + var err error + var tagMod *models.Studio + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + // Search for performer + tagMod, err = rs.Repository.Studio.FindByName(ctx, after, true) + return err + }); err == nil { + ret.Partial.StudioID.Set = true + ret.Partial.StudioID.Value = tagMod.ID + } + return true + } + return false +} +func handleAddDirector(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { + if strings.HasPrefix(tag.Name, "Director:") { + after := strings.TrimPrefix(tag.Name, "Director:") + ret.Partial.Director.Set = true + ret.Partial.Director.Value = after + return true + } + + return false +} + +// Will be overwritten if PlayCount tag is updated +func handleSetWatched(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { + prefix := string(HeresphereCustomTagWatched) + ":" + if strings.HasPrefix(tag.Name, prefix) { + after := strings.TrimPrefix(tag.Name, prefix) + if b, err := strconv.ParseBool(after); err == nil { + // Plays chicken + if b && scene.PlayCount == 0 { + ret.Partial.PlayCount.Set = true + ret.Partial.PlayCount.Value = 1 + } else if !b { + ret.Partial.PlayCount.Set = true + ret.Partial.PlayCount.Value = 0 + } + } + return true + } + return false +} +func handleSetOrganized(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { + prefix := string(HeresphereCustomTagOrganized) + ":" + if strings.HasPrefix(tag.Name, prefix) { + after := strings.TrimPrefix(tag.Name, prefix) + if b, err := strconv.ParseBool(after); err == nil { + ret.Partial.Organized.Set = true + ret.Partial.Organized.Value = b + } + return true + } + return false +} +func handleSetRated(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { + prefix := string(HeresphereCustomTagRated) + ":" + if strings.HasPrefix(tag.Name, prefix) { + after := strings.TrimPrefix(tag.Name, prefix) + if b, err := strconv.ParseBool(after); err == nil && !b { + ret.Partial.Rating.Set = true + ret.Partial.Rating.Null = true + } + return true + } + return false +} +func handleSetPlayCount(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { + prefix := string(HeresphereCustomTagPlayCount) + ":" + if strings.HasPrefix(tag.Name, prefix) { + after := strings.TrimPrefix(tag.Name, prefix) + if numRes, err := strconv.Atoi(after); err != nil { + ret.Partial.PlayCount.Set = true + ret.Partial.PlayCount.Value = numRes + } + return true + } + return false +} +func handleSetOCount(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { + prefix := string(HeresphereCustomTagOCounter) + ":" + if strings.HasPrefix(tag.Name, prefix) { + after := strings.TrimPrefix(tag.Name, prefix) + if numRes, err := strconv.Atoi(after); err != nil { + ret.Partial.OCounter.Set = true + ret.Partial.OCounter.Value = numRes + } + return true + } + return false +} From ba7fd1e3451394098f8c19bcf982dd4b7e0a989d Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 31 Aug 2023 16:29:23 +0200 Subject: [PATCH 118/144] More tag refactor + marker bugfix --- internal/heresphere/tags.go | 346 +++++++++++++++++++----------------- 1 file changed, 184 insertions(+), 162 deletions(-) diff --git a/internal/heresphere/tags.go b/internal/heresphere/tags.go index 896349fabd9..cf8826f9ab9 100644 --- a/internal/heresphere/tags.go +++ b/internal/heresphere/tags.go @@ -293,221 +293,243 @@ func handleTags(ctx context.Context, scn *models.Scene, user *HeresphereAuthReq, } func handleAddTag(ctx context.Context, rs Routes, tag HeresphereVideoTag, tagIDs *[]int) bool { - if strings.HasPrefix(tag.Name, "Tag:") { - after := strings.TrimPrefix(tag.Name, "Tag:") - var err error - var tagMod *models.Tag - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - // Search for tag - tagMod, err = rs.Repository.Tag.FindByName(ctx, after, true) - return err - }); err != nil { - fmt.Printf("Heresphere handleTags Tag.FindByName error: %s\n", err.Error()) - tagMod = nil - } + if !strings.HasPrefix(tag.Name, "Tag:") { + return false + } - if tagMod != nil { - *tagIDs = append(*tagIDs, tagMod.ID) - } - return true + after := strings.TrimPrefix(tag.Name, "Tag:") + var err error + var tagMod *models.Tag + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + // Search for tag + tagMod, err = rs.Repository.Tag.FindByName(ctx, after, true) + return err + }); err != nil { + fmt.Printf("Heresphere handleTags Tag.FindByName error: %s\n", err.Error()) + tagMod = nil + } + + if tagMod != nil { + *tagIDs = append(*tagIDs, tagMod.ID) } - return false + + return true } func handleAddPerformer(ctx context.Context, rs Routes, tag HeresphereVideoTag, perfIDs *[]int) bool { - if strings.HasPrefix(tag.Name, "Performer:") { - after := strings.TrimPrefix(tag.Name, "Performer:") - var err error - var tagMod *models.Performer - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - var tagMods []*models.Performer + if !strings.HasPrefix(tag.Name, "Performer:") { + return false + } - // Search for performer - if tagMods, err = rs.Repository.Performer.FindByNames(ctx, []string{after}, true); err == nil && len(tagMods) > 0 { - tagMod = tagMods[0] - } + after := strings.TrimPrefix(tag.Name, "Performer:") + var err error + var tagMod *models.Performer + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + var tagMods []*models.Performer - return err - }); err != nil { - fmt.Printf("Heresphere handleTags Performer.FindByNames error: %s\n", err.Error()) - tagMod = nil + // Search for performer + if tagMods, err = rs.Repository.Performer.FindByNames(ctx, []string{after}, true); err == nil && len(tagMods) > 0 { + tagMod = tagMods[0] } - if tagMod != nil { - *perfIDs = append(*perfIDs, tagMod.ID) - } - return true + return err + }); err != nil { + fmt.Printf("Heresphere handleTags Performer.FindByNames error: %s\n", err.Error()) + tagMod = nil } - return false + + if tagMod != nil { + *perfIDs = append(*perfIDs, tagMod.ID) + } + + return true } func handleAddMarker(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene) bool { - if strings.HasPrefix(tag.Name, "Marker:") { - after := strings.TrimPrefix(tag.Name, "Marker:") - var tagId *string - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - var err error - var markerResult []*models.MarkerStringsResultType - searchType := "count" - - // Search for marker - if markerResult, err = rs.Repository.SceneMarker.GetMarkerStrings(ctx, &after, &searchType); err == nil && len(markerResult) > 0 { - tagId = &markerResult[0].ID - - // Search for tag - if markers, err := rs.Repository.SceneMarker.FindBySceneID(ctx, scene.ID); err == nil { - i, e := strconv.Atoi(*tagId) - if e == nil { - // Note: Currently we search if a marker exists. - // If it doesn't, create it. - // This also means that markers CANNOT be deleted using the api. - for _, marker := range markers { - if marker.Seconds == tag.Start && - marker.SceneID == scene.ID && - marker.PrimaryTagID == i { - tagId = nil - } + if !strings.HasPrefix(tag.Name, "Marker:") { + return false + } + + after := strings.TrimPrefix(tag.Name, "Marker:") + var tagId *string + + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + var err error + var markerResult []*models.MarkerStringsResultType + searchType := "count" + + // Search for marker + if markerResult, err = rs.Repository.SceneMarker.GetMarkerStrings(ctx, &after, &searchType); len(markerResult) > 0 { + tagId = &markerResult[0].ID + + // Search for tag + if markers, err := rs.Repository.SceneMarker.FindBySceneID(ctx, scene.ID); err == nil { + i, e := strconv.Atoi(*tagId) + if e == nil { + // Note: Currently we search if a marker exists. + // If it doesn't, create it. + // This also means that markers CANNOT be deleted using the api. + for _, marker := range markers { + if marker.Seconds == tag.Start && + marker.SceneID == scene.ID && + marker.PrimaryTagID == i { + tagId = nil } } } } + } - return err - }); err != nil { - // Create marker - i, e := strconv.Atoi(*tagId) - if tagId != nil && e == nil { - currentTime := time.Now() - newMarker := models.SceneMarker{ - Title: "", - Seconds: tag.Start, - PrimaryTagID: i, - SceneID: scene.ID, - CreatedAt: currentTime, - UpdatedAt: currentTime, - } + return err + }); tagId != nil { + // Create marker + i, e := strconv.Atoi(*tagId) + if e == nil { + currentTime := time.Now() + newMarker := models.SceneMarker{ + Title: "", + Seconds: tag.Start, + PrimaryTagID: i, + SceneID: scene.ID, + CreatedAt: currentTime, + UpdatedAt: currentTime, + } - if rs.Repository.SceneMarker.Create(ctx, &newMarker) != nil { - logger.Errorf("Heresphere handleTags SceneMarker.Create error: %s\n", err.Error()) - } + if rs.Repository.SceneMarker.Create(ctx, &newMarker) != nil { + logger.Errorf("Heresphere handleTags SceneMarker.Create error: %s\n", err.Error()) } } - return true } - return false + + return true } func handleAddMovie(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { - if strings.HasPrefix(tag.Name, "Movie:") { - after := strings.TrimPrefix(tag.Name, "Movie:") + if !strings.HasPrefix(tag.Name, "Movie:") { + return false + } - var err error - var tagMod *models.Movie - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - // Search for performer - tagMod, err = rs.Repository.Movie.FindByName(ctx, after, true) - return err - }); err == nil { - ret.Partial.MovieIDs.Mode = models.RelationshipUpdateModeSet - ret.Partial.MovieIDs.AddUnique(models.MoviesScenes{ - MovieID: tagMod.ID, - SceneIndex: &scene.ID, - }) - } - return true + after := strings.TrimPrefix(tag.Name, "Movie:") + + var err error + var tagMod *models.Movie + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + // Search for performer + tagMod, err = rs.Repository.Movie.FindByName(ctx, after, true) + return err + }); err == nil { + ret.Partial.MovieIDs.Mode = models.RelationshipUpdateModeSet + ret.Partial.MovieIDs.AddUnique(models.MoviesScenes{ + MovieID: tagMod.ID, + SceneIndex: &scene.ID, + }) } - return false + + return true } func handleAddStudio(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { - if strings.HasPrefix(tag.Name, "Studio:") { - after := strings.TrimPrefix(tag.Name, "Studio:") + if !strings.HasPrefix(tag.Name, "Studio:") { + return false + } - var err error - var tagMod *models.Studio - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - // Search for performer - tagMod, err = rs.Repository.Studio.FindByName(ctx, after, true) - return err - }); err == nil { - ret.Partial.StudioID.Set = true - ret.Partial.StudioID.Value = tagMod.ID - } - return true + after := strings.TrimPrefix(tag.Name, "Studio:") + + var err error + var tagMod *models.Studio + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + // Search for performer + tagMod, err = rs.Repository.Studio.FindByName(ctx, after, true) + return err + }); err == nil { + ret.Partial.StudioID.Set = true + ret.Partial.StudioID.Value = tagMod.ID } - return false + + return true } func handleAddDirector(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { - if strings.HasPrefix(tag.Name, "Director:") { - after := strings.TrimPrefix(tag.Name, "Director:") - ret.Partial.Director.Set = true - ret.Partial.Director.Value = after - return true + if !strings.HasPrefix(tag.Name, "Director:") { + return false } - return false + after := strings.TrimPrefix(tag.Name, "Director:") + ret.Partial.Director.Set = true + ret.Partial.Director.Value = after + + return true } // Will be overwritten if PlayCount tag is updated func handleSetWatched(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { prefix := string(HeresphereCustomTagWatched) + ":" - if strings.HasPrefix(tag.Name, prefix) { - after := strings.TrimPrefix(tag.Name, prefix) - if b, err := strconv.ParseBool(after); err == nil { - // Plays chicken - if b && scene.PlayCount == 0 { - ret.Partial.PlayCount.Set = true - ret.Partial.PlayCount.Value = 1 - } else if !b { - ret.Partial.PlayCount.Set = true - ret.Partial.PlayCount.Value = 0 - } + if !strings.HasPrefix(tag.Name, prefix) { + return false + } + + after := strings.TrimPrefix(tag.Name, prefix) + if b, err := strconv.ParseBool(after); err == nil { + // Plays chicken + if b && scene.PlayCount == 0 { + ret.Partial.PlayCount.Set = true + ret.Partial.PlayCount.Value = 1 + } else if !b { + ret.Partial.PlayCount.Set = true + ret.Partial.PlayCount.Value = 0 } - return true } - return false + + return true } func handleSetOrganized(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { prefix := string(HeresphereCustomTagOrganized) + ":" - if strings.HasPrefix(tag.Name, prefix) { - after := strings.TrimPrefix(tag.Name, prefix) - if b, err := strconv.ParseBool(after); err == nil { - ret.Partial.Organized.Set = true - ret.Partial.Organized.Value = b - } - return true + if !strings.HasPrefix(tag.Name, prefix) { + return false } - return false + + after := strings.TrimPrefix(tag.Name, prefix) + if b, err := strconv.ParseBool(after); err == nil { + ret.Partial.Organized.Set = true + ret.Partial.Organized.Value = b + } + + return true } func handleSetRated(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { prefix := string(HeresphereCustomTagRated) + ":" - if strings.HasPrefix(tag.Name, prefix) { - after := strings.TrimPrefix(tag.Name, prefix) - if b, err := strconv.ParseBool(after); err == nil && !b { - ret.Partial.Rating.Set = true - ret.Partial.Rating.Null = true - } - return true + if !strings.HasPrefix(tag.Name, prefix) { + return false + } + + after := strings.TrimPrefix(tag.Name, prefix) + if b, err := strconv.ParseBool(after); err == nil && !b { + ret.Partial.Rating.Set = true + ret.Partial.Rating.Null = true } - return false + + return true } func handleSetPlayCount(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { prefix := string(HeresphereCustomTagPlayCount) + ":" - if strings.HasPrefix(tag.Name, prefix) { - after := strings.TrimPrefix(tag.Name, prefix) - if numRes, err := strconv.Atoi(after); err != nil { - ret.Partial.PlayCount.Set = true - ret.Partial.PlayCount.Value = numRes - } - return true + if !strings.HasPrefix(tag.Name, prefix) { + return false } - return false + + after := strings.TrimPrefix(tag.Name, prefix) + if numRes, err := strconv.Atoi(after); err != nil { + ret.Partial.PlayCount.Set = true + ret.Partial.PlayCount.Value = numRes + } + + return true } func handleSetOCount(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { prefix := string(HeresphereCustomTagOCounter) + ":" - if strings.HasPrefix(tag.Name, prefix) { - after := strings.TrimPrefix(tag.Name, prefix) - if numRes, err := strconv.Atoi(after); err != nil { - ret.Partial.OCounter.Set = true - ret.Partial.OCounter.Value = numRes - } - return true + if !strings.HasPrefix(tag.Name, prefix) { + return false + } + + after := strings.TrimPrefix(tag.Name, prefix) + if numRes, err := strconv.Atoi(after); err != nil { + ret.Partial.OCounter.Set = true + ret.Partial.OCounter.Value = numRes } - return false + + return true } From 2bf88b96dd70a54b49e20aa1430a91f54a46a3f9 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 31 Aug 2023 16:39:34 +0200 Subject: [PATCH 119/144] Might aswell use LoginPlain --- pkg/session/session.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/session/session.go b/pkg/session/session.go index 3e266882339..db36adb28e3 100644 --- a/pkg/session/session.go +++ b/pkg/session/session.go @@ -77,8 +77,8 @@ func (s *Store) Login(w http.ResponseWriter, r *http.Request) error { password := r.FormValue(passwordFormKey) // authenticate the user - if !s.config.ValidateCredentials(username, password) { - return &InvalidCredentialsError{Username: username} + if err := s.LoginPlain(username, password); err != nil { + return err } // since we only have one user, don't leak the name From 6513e3392c830f908436918ba221f717ca99d47e Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 1 Sep 2023 23:40:09 +0200 Subject: [PATCH 120/144] Update for model refactor --- internal/heresphere/interfaces.go | 6 +++--- internal/heresphere/media.go | 3 +-- internal/heresphere/routes.go | 4 ++-- internal/heresphere/update.go | 5 ++--- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/internal/heresphere/interfaces.go b/internal/heresphere/interfaces.go index 44f3c928121..7ab886e2c0c 100644 --- a/internal/heresphere/interfaces.go +++ b/internal/heresphere/interfaces.go @@ -1,10 +1,10 @@ package heresphere import ( - "github.com/stashapp/stash/pkg/scene" + "github.com/stashapp/stash/pkg/models" ) type sceneFinder interface { - scene.Queryer - scene.IDFinder + models.SceneQueryer + models.SceneGetter } diff --git a/internal/heresphere/media.go b/internal/heresphere/media.go index c97a83d2362..0a1f76018a2 100644 --- a/internal/heresphere/media.go +++ b/internal/heresphere/media.go @@ -9,7 +9,6 @@ import ( "github.com/stashapp/stash/internal/api/urlbuilders" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" - "github.com/stashapp/stash/pkg/file" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/txn" @@ -98,7 +97,7 @@ func getVideoSubtitles(rs Routes, r *http.Request, scene *models.Scene) []Heresp /* * Function to get transcoded media sources */ -func getTranscodedMediaSources(sceneURL string, transcodeSize int, mediaFile *file.VideoFile) map[string][]HeresphereVideoMediaSource { +func getTranscodedMediaSources(sceneURL string, transcodeSize int, mediaFile *models.VideoFile) map[string][]HeresphereVideoMediaSource { transcodedSources := make(map[string][]HeresphereVideoMediaSource) transNames := []string{"HLS", "DASH"} resRatio := float32(mediaFile.Width) / float32(mediaFile.Height) diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index b0bbd4619ee..60b7ce1a41c 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -41,7 +41,7 @@ const ( type Routes struct { TxnManager txn.Manager SceneFinder sceneFinder - FileFinder file.Finder + FileFinder models.FileFinder Repository manager.Repository } @@ -205,7 +205,7 @@ func (rs Routes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { // Create scene URLs sceneUrls := make([]string, len(scenes)) for idx, scene := range scenes { - sceneUrls[idx] = fmt.Sprintf("%s/heresphere/%d", manager.GetBaseURL(r), scene.ID) + sceneUrls[idx] = addApiKey(fmt.Sprintf("%s/heresphere/%d", manager.GetBaseURL(r), scene.ID)) } // All library diff --git a/internal/heresphere/update.go b/internal/heresphere/update.go index 688960d48c5..7c176f64c76 100644 --- a/internal/heresphere/update.go +++ b/internal/heresphere/update.go @@ -3,7 +3,6 @@ package heresphere import ( "context" - "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/pkg/file" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" @@ -23,7 +22,7 @@ func updateRating(user HeresphereAuthReq, ret *scene.UpdateSet) (bool, error) { /* * Modifies the scene PlayCount */ -func updatePlayCount(ctx context.Context, scn *models.Scene, event HeresphereVideoEvent, txnManager txn.Manager, fqb manager.SceneReaderWriter) error { +func updatePlayCount(ctx context.Context, scn *models.Scene, event HeresphereVideoEvent, txnManager txn.Manager, fqb models.SceneReaderWriter) error { if per, err := getMinPlayPercent(); err == nil { newTime := event.Time / 1000 file := scn.Files.Primary() @@ -51,7 +50,7 @@ func updatePlayCount(ctx context.Context, scn *models.Scene, event HeresphereVid /* * Deletes the scene's primary file */ -func handleDeletePrimaryFile(ctx context.Context, txnManager txn.Manager, scn *models.Scene, fqb manager.FileReaderWriter, fileDeleter *file.Deleter) (bool, error) { +func handleDeletePrimaryFile(ctx context.Context, txnManager txn.Manager, scn *models.Scene, fqb models.FileReaderWriter, fileDeleter *file.Deleter) (bool, error) { err := txn.WithTxn(ctx, txnManager, func(ctx context.Context) error { if err := scn.LoadPrimaryFile(ctx, fqb); err != nil { return err From 81a92755dcc5d8630bcc041dafd60135946854b3 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Sat, 2 Sep 2023 01:01:11 +0200 Subject: [PATCH 121/144] Fix linter error --- internal/api/types.go | 11 ----------- internal/manager/json_utils.go | 8 -------- 2 files changed, 19 deletions(-) diff --git a/internal/api/types.go b/internal/api/types.go index 79b4aa02002..bb46a5f8929 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -2,22 +2,11 @@ package api import ( "fmt" - "math" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) -// #1572 - Inf and NaN values cause the JSON marshaller to fail -// Return nil for these values -func handleFloat64(v float64) *float64 { - if math.IsInf(v, 0) || math.IsNaN(v) { - return nil - } - - return &v -} - func translateUpdateIDs(strIDs []string, mode models.RelationshipUpdateMode) (*models.UpdateIDs, error) { ids, err := stringslice.StringSliceToIntSlice(strIDs) if err != nil { diff --git a/internal/manager/json_utils.go b/internal/manager/json_utils.go index 6f68e487aaf..4daf2889dc4 100644 --- a/internal/manager/json_utils.go +++ b/internal/manager/json_utils.go @@ -53,11 +53,3 @@ func HandleFloat64(v float64) *float64 { return &v } - -func HandleFloat64Value(v float64) float64 { - if math.IsInf(v, 0) || math.IsNaN(v) { - return 0 - } - - return v -} From e3b70d187c9e8e5307094ab085f0afcd889b682e Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Sat, 2 Sep 2023 01:05:47 +0200 Subject: [PATCH 122/144] Missed one --- internal/heresphere/routes.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 60b7ce1a41c..015cae3dfe8 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -311,7 +311,9 @@ func (rs Routes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { if scene.Files.PrimaryLoaded() { file_ids := scene.Files.Primary() if file_ids != nil { - processedScene.Duration = manager.HandleFloat64Value(file_ids.Duration * 1000.0) + if val := manager.HandleFloat64(file_ids.Duration * 1000.0); val != nil { + processedScene.Duration = *val + } } } From a58df7091caee1760429e46e97562039ab7678f7 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Sun, 3 Sep 2023 03:55:41 +0200 Subject: [PATCH 123/144] Add filter parsing to HSP API This commit is quite late and also quite a bit of code, so if it's deemed too late please just write. --- internal/heresphere/filter_structs.go | 285 ++++++++++++++++++++++++++ internal/heresphere/filters.go | 107 ++++++++++ internal/heresphere/routes.go | 53 ++--- 3 files changed, 414 insertions(+), 31 deletions(-) create mode 100644 internal/heresphere/filter_structs.go create mode 100644 internal/heresphere/filters.go diff --git a/internal/heresphere/filter_structs.go b/internal/heresphere/filter_structs.go new file mode 100644 index 00000000000..067a751c3b7 --- /dev/null +++ b/internal/heresphere/filter_structs.go @@ -0,0 +1,285 @@ +package heresphere + +import ( + "github.com/stashapp/stash/pkg/models" +) + +type StringSingletonInput struct { + Value string `json:"value"` +} + +// Technically the "modifier" in IntCriterionInput is redundant when i do this, meh +type IntCriterionStored struct { + Modifier models.CriterionModifier `json:"modifier"` + Value models.IntCriterionInput `json:"value"` +} + +func (m IntCriterionStored) ToOriginal() *models.IntCriterionInput { + obj := m.Value + obj.Modifier = m.Modifier + return &obj +} + +type DateCriterionStored struct { + Modifier models.CriterionModifier `json:"modifier"` + Value models.DateCriterionInput `json:"value"` +} + +func (m DateCriterionStored) ToOriginal() *models.DateCriterionInput { + obj := m.Value + obj.Modifier = m.Modifier + return &obj +} + +type TimeCriterionStored struct { + Modifier models.CriterionModifier `json:"modifier"` + Value models.TimestampCriterionInput `json:"value"` +} + +func (m TimeCriterionStored) ToOriginal() *models.TimestampCriterionInput { + obj := m.Value + obj.Modifier = m.Modifier + return &obj +} + +type HierarchicalMultiCriterionInputStoredEntry struct { + Id string `json:"id"` + Label string `json:"label"` +} +type HierarchicalMultiCriterionInputStoredAux struct { + Items *[]HierarchicalMultiCriterionInputStoredEntry `json:"items"` + Depth *int `json:"depth"` + Excludes *[]HierarchicalMultiCriterionInputStoredEntry `json:"excludes"` +} +type HierarchicalMultiCriterionInputStored struct { + Modifier models.CriterionModifier `json:"modifier"` + Value HierarchicalMultiCriterionInputStoredAux `json:"value"` +} + +func (m HierarchicalMultiCriterionInputStored) ToHierarchicalCriterion() *models.HierarchicalMultiCriterionInput { + data := &models.HierarchicalMultiCriterionInput{ + Value: []string{}, + Modifier: m.Modifier, + Depth: m.Value.Depth, + Excludes: []string{}, + } + + if m.Value.Items != nil { + for _, entry := range *m.Value.Items { + data.Value = append(data.Value, entry.Id) + } + } + if m.Value.Excludes != nil { + for _, entry := range *m.Value.Excludes { + data.Excludes = append(data.Excludes, entry.Id) + } + } + + return data +} +func (m HierarchicalMultiCriterionInputStored) ToMultiCriterion() *models.MultiCriterionInput { + filled := m.ToHierarchicalCriterion() + return &models.MultiCriterionInput{ + Value: filled.Value, + Modifier: m.Modifier, + Excludes: filled.Excludes, + } +} + +type SceneFilterTypeStored struct { + And *SceneFilterTypeStored `json:"AND"` + Or *SceneFilterTypeStored `json:"OR"` + Not *SceneFilterTypeStored `json:"NOT"` + ID *IntCriterionStored `json:"id"` + Title *models.StringCriterionInput `json:"title"` + Code *models.StringCriterionInput `json:"code"` + Details *models.StringCriterionInput `json:"details"` + Director *models.StringCriterionInput `json:"director"` + // Filter by file oshash + Oshash *models.StringCriterionInput `json:"oshash"` + // Filter by file checksum + Checksum *models.StringCriterionInput `json:"checksum"` + // Filter by file phash + Phash *models.StringCriterionInput `json:"phash"` + // Filter by phash distance + PhashDistance *models.PhashDistanceCriterionInput `json:"phash_distance"` + // Filter by path + Path *models.StringCriterionInput `json:"path"` + // Filter by file count + FileCount *IntCriterionStored `json:"file_count"` + // Filter by rating expressed as 1-5 + Rating *IntCriterionStored `json:"rating"` + // Filter by rating expressed as 1-100 + Rating100 *IntCriterionStored `json:"rating100"` + // Filter by organized + Organized *StringSingletonInput `json:"organized"` + // Filter by o-counter + OCounter *IntCriterionStored `json:"o_counter"` + // Filter Scenes that have an exact phash match available + Duplicated *models.PHashDuplicationCriterionInput `json:"duplicated"` + // Filter by resolution + Resolution *models.ResolutionCriterionInput `json:"resolution"` + // Filter by video codec + VideoCodec *models.StringCriterionInput `json:"video_codec"` + // Filter by audio codec + AudioCodec *models.StringCriterionInput `json:"audio_codec"` + // Filter by duration (in seconds) + Duration *IntCriterionStored `json:"duration"` + // Filter to only include scenes which have markers. `true` or `false` + HasMarkers *models.StringCriterionInput `json:"has_markers"` + // Filter to only include scenes missing this property + IsMissing *models.StringCriterionInput `json:"is_missing"` + // Filter to only include scenes with this studio + Studios *HierarchicalMultiCriterionInputStored `json:"studios"` + // Filter to only include scenes with this movie + Movies *HierarchicalMultiCriterionInputStored `json:"movies"` + // Filter to only include scenes with these tags + Tags *HierarchicalMultiCriterionInputStored `json:"tags"` + // Filter by tag count + TagCount *IntCriterionStored `json:"tag_count"` + // Filter to only include scenes with performers with these tags + PerformerTags *HierarchicalMultiCriterionInputStored `json:"performer_tags"` + // Filter scenes that have performers that have been favorited + PerformerFavorite *StringSingletonInput `json:"performer_favorite"` + // Filter scenes by performer age at time of scene + PerformerAge *IntCriterionStored `json:"performer_age"` + // Filter to only include scenes with these performers + Performers *HierarchicalMultiCriterionInputStored `json:"performers"` + // Filter by performer count + PerformerCount *IntCriterionStored `json:"performer_count"` + // Filter by StashID + StashID *models.StringCriterionInput `json:"stash_id"` + // Filter by StashID Endpoint + StashIDEndpoint *models.StashIDCriterionInput `json:"stash_id_endpoint"` + // Filter by url + URL *models.StringCriterionInput `json:"url"` + // Filter by interactive + Interactive *StringSingletonInput `json:"interactive"` + // Filter by InteractiveSpeed + InteractiveSpeed *IntCriterionStored `json:"interactive_speed"` + // Filter by captions + Captions *models.StringCriterionInput `json:"captions"` + // Filter by resume time + ResumeTime *IntCriterionStored `json:"resume_time"` + // Filter by play count + PlayCount *IntCriterionStored `json:"play_count"` + // Filter by play duration (in seconds) + PlayDuration *IntCriterionStored `json:"play_duration"` + // Filter by date + Date *DateCriterionStored `json:"date"` + // Filter by created at + CreatedAt *TimeCriterionStored `json:"created_at"` + // Filter by updated at + UpdatedAt *TimeCriterionStored `json:"updated_at"` +} + +func (fsf SceneFilterTypeStored) ToOriginal() *models.SceneFilterType { + model := models.SceneFilterType{ + Title: fsf.Title, + Code: fsf.Code, + Details: fsf.Details, + Director: fsf.Director, + Oshash: fsf.Oshash, + Checksum: fsf.Checksum, + Phash: fsf.Phash, + Path: fsf.Path, + Duplicated: fsf.Duplicated, + Resolution: fsf.Resolution, + VideoCodec: fsf.VideoCodec, + AudioCodec: fsf.AudioCodec, + StashID: fsf.StashID, + StashIDEndpoint: fsf.StashIDEndpoint, + URL: fsf.URL, + Captions: fsf.Captions, + } + + if fsf.And != nil { + model.And = fsf.And.ToOriginal() + } + if fsf.Or != nil { + model.Or = fsf.Or.ToOriginal() + } + if fsf.Not != nil { + model.Not = fsf.Not.ToOriginal() + } + if fsf.ID != nil { + model.ID = fsf.ID.ToOriginal() + } + if fsf.FileCount != nil { + model.FileCount = fsf.FileCount.ToOriginal() + } + if fsf.Rating100 != nil { + model.Rating = fsf.Rating100.ToOriginal() + } + if fsf.Organized != nil { + b := fsf.Organized.Value == "true" + model.Organized = &b + } + if fsf.OCounter != nil { + model.OCounter = fsf.OCounter.ToOriginal() + } + if fsf.Duration != nil { + model.Duration = fsf.Duration.ToOriginal() + } + if fsf.HasMarkers != nil { + model.HasMarkers = &fsf.HasMarkers.Value + } + if fsf.IsMissing != nil { + model.IsMissing = &fsf.IsMissing.Value + } + if fsf.Studios != nil { + model.Studios = fsf.Studios.ToHierarchicalCriterion() + } + if fsf.Movies != nil { + model.Movies = fsf.Movies.ToMultiCriterion() + } + if fsf.Tags != nil { + model.Tags = fsf.Tags.ToHierarchicalCriterion() + } + if fsf.TagCount != nil { + model.TagCount = fsf.TagCount.ToOriginal() + } + if fsf.PerformerTags != nil { + model.PerformerTags = fsf.PerformerTags.ToHierarchicalCriterion() + } + if fsf.PerformerFavorite != nil { + b := fsf.PerformerFavorite.Value == "true" + model.PerformerFavorite = &b + } + if fsf.PerformerAge != nil { + model.PerformerAge = fsf.PerformerAge.ToOriginal() + } + if fsf.Performers != nil { + model.Performers = fsf.Performers.ToMultiCriterion() + } + if fsf.PerformerCount != nil { + model.PerformerCount = fsf.PerformerCount.ToOriginal() + } + if fsf.Interactive != nil { + b := fsf.Interactive.Value == "true" + model.Interactive = &b + } + if fsf.InteractiveSpeed != nil { + model.InteractiveSpeed = fsf.InteractiveSpeed.ToOriginal() + } + if fsf.ResumeTime != nil { + model.ResumeTime = fsf.ResumeTime.ToOriginal() + } + if fsf.PlayCount != nil { + model.PlayCount = fsf.PlayCount.ToOriginal() + } + if fsf.PlayDuration != nil { + model.PlayDuration = fsf.PlayDuration.ToOriginal() + } + if fsf.Date != nil { + model.Date = fsf.Date.ToOriginal() + } + if fsf.CreatedAt != nil { + model.CreatedAt = fsf.CreatedAt.ToOriginal() + } + if fsf.UpdatedAt != nil { + model.UpdatedAt = fsf.UpdatedAt.ToOriginal() + } + + return &model +} diff --git a/internal/heresphere/filters.go b/internal/heresphere/filters.go new file mode 100644 index 00000000000..7dc3e6ed919 --- /dev/null +++ b/internal/heresphere/filters.go @@ -0,0 +1,107 @@ +package heresphere + +import ( + "context" + "fmt" + + "github.com/mitchellh/mapstructure" + "github.com/stashapp/stash/internal/manager" + "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/txn" +) + +func parseObjectFilter(sf *models.SavedFilter) (*models.SceneFilterType, error) { + var result SceneFilterTypeStored + + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &result, + TagName: "json", + ErrorUnused: false, + ErrorUnset: false, + WeaklyTypedInput: true, + }) + if err != nil { + return nil, fmt.Errorf("Error creating decoder: %s", err) + } + + if err := decoder.Decode(sf.ObjectFilter); err != nil { + return nil, fmt.Errorf("Error decoding map to struct: %s", err) + } + + return result.ToOriginal(), nil +} + +func getAllFilters(ctx context.Context, repo manager.Repository) (scenesMap map[string][]int, err error) { + scenesMap = make(map[string][]int) // Initialize scenesMap + + savedfilters, err := func() ([]*models.SavedFilter, error) { + var filters []*models.SavedFilter + err = txn.WithReadTxn(ctx, repo.TxnManager, func(ctx context.Context) error { + filters, err = repo.SavedFilter.FindByMode(ctx, models.FilterModeScenes) + return err + }) + return filters, err + }() + + if err != nil { + err = fmt.Errorf("heresphere FilterTest SavedFilter.FindByMode error: %s", err.Error()) + return + } + + for _, savedfilter := range savedfilters { + filter := savedfilter.FindFilter + sceneFilter, err := parseObjectFilter(savedfilter) + + if err != nil { + logger.Errorf("Heresphere FilterTest parseObjectFilter error: %s\n", err.Error()) + continue + } + + if filter != nil && filter.Q != nil && len(*filter.Q) > 0 { + sceneFilter.Path = &models.StringCriterionInput{ + Modifier: models.CriterionModifierMatchesRegex, + Value: "(?i)" + *filter.Q, + } + } + + // make a copy of the filter if provided, nilling out Q + var queryFilter *models.FindFilterType + if filter != nil { + f := *filter + queryFilter = &f + queryFilter.Q = nil + + page := 0 + perpage := -1 + queryFilter.Page = &page + queryFilter.PerPage = &perpage + } + + var scenes *models.SceneQueryResult + err = txn.WithReadTxn(ctx, repo.TxnManager, func(ctx context.Context) error { + var err error + scenes, err = repo.Scene.Query(ctx, models.SceneQueryOptions{ + QueryOptions: models.QueryOptions{ + FindFilter: queryFilter, + Count: false, + }, + SceneFilter: sceneFilter, + TotalDuration: false, + TotalSize: false, + }) + + return err + }) + + if err != nil { + logger.Errorf("Heresphere FilterTest SceneQuery error: %s\n", err.Error()) + continue + } + + name := savedfilter.Name + scenesMap[name] = append(scenesMap[name], scenes.QueryResult.IDs...) + } + + return +} diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 015cae3dfe8..6321d76211f 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -185,46 +185,37 @@ func (rs Routes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { Link: fmt.Sprintf("%s%s", manager.GetBaseURL(r), "/"), } - // Read scenes - scenes, err := func() ([]*models.Scene, error) { - var scenes []*models.Scene - err := txn.WithReadTxn(r.Context(), rs.Repository.TxnManager, func(ctx context.Context) error { - var err error - scenes, err = rs.Repository.Scene.All(ctx) - return err - }) - return scenes, err - }() - - if err != nil { - logger.Errorf("Heresphere HeresphereIndex SceneAll error: %s\n", err.Error()) - http.Error(w, "Failed to fetch scenes!", http.StatusInternalServerError) - return - } - - // Create scene URLs - sceneUrls := make([]string, len(scenes)) - for idx, scene := range scenes { - sceneUrls[idx] = addApiKey(fmt.Sprintf("%s/heresphere/%d", manager.GetBaseURL(r), scene.ID)) - } - - // All library - library := HeresphereIndexEntry{ - Name: "All", - List: sceneUrls, - } // Index - idx := HeresphereIndex{ + libraryObj := HeresphereIndex{ Access: HeresphereMember, Banner: banner, - Library: []HeresphereIndexEntry{library}, + Library: []HeresphereIndexEntry{}, + } + + // Add filters + parsedFilters, err := getAllFilters(r.Context(), rs.Repository) + if err == nil { + for key, value := range parsedFilters { + sceneUrls := make([]string, len(value)) + + for idx, sceneID := range value { + sceneUrls[idx] = addApiKey(fmt.Sprintf("%s/heresphere/%d", manager.GetBaseURL(r), sceneID)) + } + + libraryObj.Library = append(libraryObj.Library, HeresphereIndexEntry{ + Name: key, + List: sceneUrls, + }) + } + } else { + logger.Warnf("Heresphere HeresphereIndex getAllFilters error: %s\n", err.Error()) } // Set response headers and encode JSON w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) enc.SetEscapeHTML(false) - if err := enc.Encode(idx); err != nil { + if err := enc.Encode(libraryObj); err != nil { logger.Errorf("Heresphere HeresphereIndex encode error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return From bbe4d02b29024e344c6b8bbe85826cb2f939df10 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 6 Sep 2023 16:01:49 +0200 Subject: [PATCH 124/144] Split up tags even more --- internal/heresphere/tags_read.go | 335 ++++++++++++++++++ .../heresphere/{tags.go => tags_write.go} | 212 ----------- 2 files changed, 335 insertions(+), 212 deletions(-) create mode 100644 internal/heresphere/tags_read.go rename internal/heresphere/{tags.go => tags_write.go} (60%) diff --git a/internal/heresphere/tags_read.go b/internal/heresphere/tags_read.go new file mode 100644 index 00000000000..5250da21bad --- /dev/null +++ b/internal/heresphere/tags_read.go @@ -0,0 +1,335 @@ +package heresphere + +import ( + "context" + "fmt" + "strconv" + + "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/txn" +) + +/* + * This auxiliary function gathers various tags from the scene to feed the api. + */ +func getVideoTags(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { + processedTags := []HeresphereVideoTag{} + + // Load all relationships + if err := loadRelationships(ctx, rs, scene); err != nil { + logger.Errorf("Heresphere getVideoTags LoadRelationships error: %s\n", err.Error()) + } + + if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + processedTags = append(processedTags, generateMarkerTags(ctx, rs, scene)...) + processedTags = append(processedTags, generateTagTags(ctx, rs, scene)...) + processedTags = append(processedTags, generatePerformerTags(ctx, rs, scene)...) + processedTags = append(processedTags, generateGalleryTags(ctx, rs, scene)...) + processedTags = append(processedTags, generateMovieTags(ctx, rs, scene)...) + processedTags = append(processedTags, generateStudioTag(ctx, rs, scene)...) + processedTags = append(processedTags, generateInteractiveTag(scene)...) + processedTags = append(processedTags, generateDirectorTag(scene)...) + processedTags = append(processedTags, generateRatingTag(scene)...) + processedTags = append(processedTags, generateWatchedTag(scene)...) + processedTags = append(processedTags, generateOrganizedTag(scene)...) + processedTags = append(processedTags, generateRatedTag(scene)...) + processedTags = append(processedTags, generateOrgasmedTag(scene)...) + processedTags = append(processedTags, generatePlayCountTag(scene)...) + processedTags = append(processedTags, generateOCounterTag(scene)...) + + return nil + }); err != nil { + logger.Errorf("Heresphere getVideoTags generate tags error: %s\n", err.Error()) + } + + return processedTags +} +func loadRelationships(ctx context.Context, rs Routes, scene *models.Scene) error { + return txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + return scene.LoadRelationships(ctx, rs.Repository.Scene) + }) +} +func generateMarkerTags(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { + // Generate marker tags + tags := []HeresphereVideoTag{} + + markIDs, err := rs.Repository.SceneMarker.FindBySceneID(ctx, scene.ID) + if err != nil { + logger.Errorf("Heresphere generateMarkerTags SceneMarker.FindBySceneID error: %s\n", err.Error()) + return tags + } + + for _, mark := range markIDs { + tagName := mark.Title + + if ret, err := rs.Repository.Tag.Find(ctx, mark.PrimaryTagID); err == nil { + if len(tagName) == 0 { + tagName = ret.Name + } else { + tagName = fmt.Sprintf("%s - %s", tagName, ret.Name) + } + } + + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Marker:%s", tagName), + Start: mark.Seconds * 1000, + End: (mark.Seconds + 60) * 1000, + } + tags = append(tags, genTag) + } + + return tags +} +func generateTagTags(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { + // Generate tag tags + tags := []HeresphereVideoTag{} + + tagIDs, err := rs.Repository.Tag.FindBySceneID(ctx, scene.ID) + if err != nil { + logger.Errorf("Heresphere generateTagTags Tag.FindBySceneID error: %s\n", err.Error()) + return tags + } + + for _, tag := range tagIDs { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Tag:%s", tag.Name), + } + tags = append(tags, genTag) + } + + return tags +} + +func generatePerformerTags(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { + // Generate performer tags + tags := []HeresphereVideoTag{} + + perfIDs, err := rs.Repository.Performer.FindBySceneID(ctx, scene.ID) + if err != nil { + logger.Errorf("Heresphere generatePerformerTags Performer.FindBySceneID error: %s\n", err.Error()) + return tags + } + + for _, perf := range perfIDs { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Performer:%s", perf.Name), + } + tags = append(tags, genTag) + } + + return tags +} + +func generateGalleryTags(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { + // Generate gallery tags + tags := []HeresphereVideoTag{} + + if scene.GalleryIDs.Loaded() { + galleries, err := rs.Repository.Gallery.FindMany(ctx, scene.GalleryIDs.List()) + if err != nil { + logger.Errorf("Heresphere generateGalleryTags Gallery.FindMany error: %s\n", err.Error()) + return tags + } + + for _, gallery := range galleries { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Gallery:%s", gallery.Title), + } + tags = append(tags, genTag) + } + } + + return tags +} + +func generateMovieTags(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { + // Generate movie tags + tags := []HeresphereVideoTag{} + + if scene.Movies.Loaded() { + lst := scene.Movies.List() + idx := make([]int, 0, len(lst)) + for _, movie := range lst { + idx = append(idx, movie.MovieID) + } + + movies, err := rs.Repository.Movie.FindMany(ctx, idx) + if err != nil { + logger.Errorf("Heresphere generateMovieTags Movie.FindMany error: %s\n", err.Error()) + return tags + } + + for _, movie := range movies { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Movie:%s", movie.Name), + } + tags = append(tags, genTag) + } + } + + return tags +} + +func generateStudioTag(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { + // Generate studio tag + tags := []HeresphereVideoTag{} + + if scene.StudioID != nil { + studio, err := rs.Repository.Studio.Find(ctx, *scene.StudioID) + if err != nil { + logger.Errorf("Heresphere generateStudioTag Studio.Find error: %s\n", err.Error()) + return tags + } + + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Studio:%s", studio.Name), + } + tags = append(tags, genTag) + } + + return tags +} + +func generateInteractiveTag(scene *models.Scene) []HeresphereVideoTag { + // Generate interactive tag + tags := []HeresphereVideoTag{} + + primaryFile := scene.Files.Primary() + if primaryFile != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagInteractive), + strconv.FormatBool(primaryFile.Interactive), + ), + } + tags = append(tags, genTag) + + if primaryFile.Interactive { + funSpeed := 0 + if primaryFile.InteractiveSpeed != nil { + funSpeed = *primaryFile.InteractiveSpeed + } + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Funspeed:%d", + funSpeed, + ), + } + tags = append(tags, genTag) + } + } + + return tags +} + +func generateDirectorTag(scene *models.Scene) []HeresphereVideoTag { + // Generate director tag + tags := []HeresphereVideoTag{} + + if len(scene.Director) > 0 { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Director:%s", scene.Director), + } + tags = append(tags, genTag) + } + + return tags +} + +func generateRatingTag(scene *models.Scene) []HeresphereVideoTag { + // Generate rating tag + tags := []HeresphereVideoTag{} + + if scene.Rating != nil { + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("Rating:%d", + models.Rating100To5(*scene.Rating), + ), + } + tags = append(tags, genTag) + } + + return tags +} + +func generateWatchedTag(scene *models.Scene) []HeresphereVideoTag { + // Generate watched tag + tags := []HeresphereVideoTag{} + + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagWatched), + strconv.FormatBool(scene.PlayCount > 0), + ), + } + tags = append(tags, genTag) + + return tags +} + +func generateOrganizedTag(scene *models.Scene) []HeresphereVideoTag { + // Generate organized tag + tags := []HeresphereVideoTag{} + + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagOrganized), + strconv.FormatBool(scene.Organized), + ), + } + tags = append(tags, genTag) + + return tags +} + +func generateRatedTag(scene *models.Scene) []HeresphereVideoTag { + // Generate rated tag + tags := []HeresphereVideoTag{} + + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagRated), + strconv.FormatBool(scene.Rating != nil), + ), + } + tags = append(tags, genTag) + + return tags +} + +func generateOrgasmedTag(scene *models.Scene) []HeresphereVideoTag { + // Generate orgasmed tag + tags := []HeresphereVideoTag{} + + genTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagOrgasmed), + strconv.FormatBool(scene.OCounter > 0), + ), + } + tags = append(tags, genTag) + + return tags +} + +func generatePlayCountTag(scene *models.Scene) []HeresphereVideoTag { + tags := []HeresphereVideoTag{} + + playCountTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagPlayCount), scene.PlayCount), + } + tags = append(tags, playCountTag) + + return tags +} + +func generateOCounterTag(scene *models.Scene) []HeresphereVideoTag { + tags := []HeresphereVideoTag{} + + oCounterTag := HeresphereVideoTag{ + Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagOCounter), scene.OCounter), + } + tags = append(tags, oCounterTag) + + return tags +} diff --git a/internal/heresphere/tags.go b/internal/heresphere/tags_write.go similarity index 60% rename from internal/heresphere/tags.go rename to internal/heresphere/tags_write.go index cf8826f9ab9..948e75ccc24 100644 --- a/internal/heresphere/tags.go +++ b/internal/heresphere/tags_write.go @@ -13,218 +13,6 @@ import ( "github.com/stashapp/stash/pkg/txn" ) -/* - * This auxiliary function gathers various tags from the scene to feed the api. - */ -func getVideoTags(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { - processedTags := []HeresphereVideoTag{} - - // Load all relationships - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - return scene.LoadRelationships(ctx, rs.Repository.Scene) - }); err != nil { - logger.Errorf("Heresphere getVideoTags LoadRelationships error: %s\n", err.Error()) - return processedTags - } - - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - mark_ids, err := rs.Repository.SceneMarker.FindBySceneID(ctx, scene.ID) - if err == nil { - for _, mark := range mark_ids { - tagName := mark.Title - - // Add tag name - if ret, err := rs.Repository.Tag.Find(ctx, mark.PrimaryTagID); err == nil { - if len(tagName) == 0 { - tagName = ret.Name - } else { - tagName = fmt.Sprintf("%s - %s", tagName, ret.Name) - } - } - - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Marker:%s", tagName), - Start: mark.Seconds * 1000, - End: (mark.Seconds + 60) * 1000, - } - processedTags = append(processedTags, genTag) - } - } else { - logger.Errorf("Heresphere getVideoTags SceneMarker.FindBySceneID error: %s\n", err.Error()) - } - - tag_ids, err := rs.Repository.Tag.FindBySceneID(ctx, scene.ID) - if err == nil { - for _, tag := range tag_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Tag:%s", tag.Name), - } - processedTags = append(processedTags, genTag) - } - } else { - logger.Errorf("Heresphere getVideoTags Tag.FindBySceneID error: %s\n", err.Error()) - } - - perf_ids, err := rs.Repository.Performer.FindBySceneID(ctx, scene.ID) - if err == nil { - for _, perf := range perf_ids { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Performer:%s", perf.Name), - } - processedTags = append(processedTags, genTag) - } - } else { - logger.Errorf("Heresphere getVideoTags Performer.FindBySceneID error: %s\n", err.Error()) - } - - if scene.GalleryIDs.Loaded() { - galleries, err := rs.Repository.Gallery.FindMany(ctx, scene.GalleryIDs.List()) - if err == nil { - for _, gallery := range galleries { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Gallery:%s", gallery.Title), - } - processedTags = append(processedTags, genTag) - } - } else { - logger.Errorf("Heresphere getVideoTags Gallery.FindMany error: %s\n", err.Error()) - } - } - - if scene.Movies.Loaded() { - lst := scene.Movies.List() - idx := make([]int, 0, len(lst)) - for _, movie := range lst { - idx = append(idx, movie.MovieID) - } - - movies, err := rs.Repository.Movie.FindMany(ctx, idx) - if err == nil { - for _, movie := range movies { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Movie:%s", movie.Name), - } - processedTags = append(processedTags, genTag) - } - } else { - logger.Errorf("Heresphere getVideoTags Movie.FindMany error: %s\n", err.Error()) - } - } - - if scene.StudioID != nil { - studio, err := rs.Repository.Studio.Find(ctx, *scene.StudioID) - if studio != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Studio:%s", studio.Name), - } - processedTags = append(processedTags, genTag) - } - if err != nil { - logger.Errorf("Heresphere getVideoTags Studio.Find error: %s\n", err.Error()) - } - } - - primaryFile := scene.Files.Primary() - if primaryFile != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagInteractive), - strconv.FormatBool(primaryFile.Interactive), - ), - } - processedTags = append(processedTags, genTag) - - if primaryFile.Interactive { - funSpeed := 0 - if primaryFile.InteractiveSpeed != nil { - funSpeed = *primaryFile.InteractiveSpeed - } - genTag = HeresphereVideoTag{ - Name: fmt.Sprintf("Funspeed:%d", - funSpeed, - ), - } - processedTags = append(processedTags, genTag) - } - } - - return err - }); err != nil { - fmt.Printf("Heresphere getVideoTags scene reader error: %s\n", err.Error()) - } - - if len(scene.Director) > 0 { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Director:%s", scene.Director), - } - processedTags = append(processedTags, genTag) - } - - if scene.Rating != nil { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("Rating:%d", - models.Rating100To5(*scene.Rating), - ), - } - processedTags = append(processedTags, genTag) - } - - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagWatched), - strconv.FormatBool(scene.PlayCount > 0), - ), - } - processedTags = append(processedTags, genTag) - } - - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagOrganized), - strconv.FormatBool(scene.Organized), - ), - } - processedTags = append(processedTags, genTag) - } - - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagRated), - strconv.FormatBool(scene.Rating != nil), - ), - } - processedTags = append(processedTags, genTag) - } - - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagOrgasmed), - strconv.FormatBool(scene.OCounter > 0), - ), - } - processedTags = append(processedTags, genTag) - } - - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagPlayCount), scene.PlayCount), - } - processedTags = append(processedTags, genTag) - } - { - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagOCounter), scene.OCounter), - } - processedTags = append(processedTags, genTag) - } - - return processedTags -} - /* * Processes tags and updates scene tags if applicable */ From a953480704328a614a95e0431975bc4826b26528 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 6 Sep 2023 16:08:01 +0200 Subject: [PATCH 125/144] Small change --- internal/heresphere/tags_read.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/internal/heresphere/tags_read.go b/internal/heresphere/tags_read.go index 5250da21bad..45cb68dc4c6 100644 --- a/internal/heresphere/tags_read.go +++ b/internal/heresphere/tags_read.go @@ -16,12 +16,9 @@ import ( func getVideoTags(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { processedTags := []HeresphereVideoTag{} - // Load all relationships - if err := loadRelationships(ctx, rs, scene); err != nil { - logger.Errorf("Heresphere getVideoTags LoadRelationships error: %s\n", err.Error()) - } - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + err := scene.LoadRelationships(ctx, rs.Repository.Scene) + processedTags = append(processedTags, generateMarkerTags(ctx, rs, scene)...) processedTags = append(processedTags, generateTagTags(ctx, rs, scene)...) processedTags = append(processedTags, generatePerformerTags(ctx, rs, scene)...) @@ -38,18 +35,13 @@ func getVideoTags(ctx context.Context, rs Routes, scene *models.Scene) []Heresph processedTags = append(processedTags, generatePlayCountTag(scene)...) processedTags = append(processedTags, generateOCounterTag(scene)...) - return nil + return err }); err != nil { logger.Errorf("Heresphere getVideoTags generate tags error: %s\n", err.Error()) } return processedTags } -func loadRelationships(ctx context.Context, rs Routes, scene *models.Scene) error { - return txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - return scene.LoadRelationships(ctx, rs.Repository.Scene) - }) -} func generateMarkerTags(ctx context.Context, rs Routes, scene *models.Scene) []HeresphereVideoTag { // Generate marker tags tags := []HeresphereVideoTag{} From 7efdd54b90782ca8e76b97fc383f3f2c62b61f29 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 11 Sep 2023 13:57:03 +0200 Subject: [PATCH 126/144] Fix playcount tracker --- internal/heresphere/routes.go | 66 +++++++++++++++++------------------ internal/heresphere/update.go | 7 ++-- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 6321d76211f..1481269f095 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -52,20 +52,20 @@ func (rs Routes) Routes() chi.Router { r := chi.NewRouter() r.Route("/", func(r chi.Router) { - r.Use(rs.HeresphereCtx) + r.Use(rs.heresphereCtx) - r.Post("/", rs.HeresphereIndex) - r.Get("/", rs.HeresphereIndex) - r.Head("/", rs.HeresphereIndex) + r.Post("/", rs.heresphereIndex) + r.Get("/", rs.heresphereIndex) + r.Head("/", rs.heresphereIndex) - r.Post("/auth", rs.HeresphereLoginToken) + r.Post("/auth", rs.heresphereLoginToken) r.Route("/{sceneId}", func(r chi.Router) { - r.Use(rs.HeresphereSceneCtx) + r.Use(rs.heresphereSceneCtx) - r.Post("/", rs.HeresphereVideoData) - r.Get("/", rs.HeresphereVideoData) + r.Post("/", rs.heresphereVideoData) + r.Get("/", rs.heresphereVideoData) - r.Post("/event", rs.HeresphereVideoEvent) + r.Post("/event", rs.heresphereVideoEvent) }) }) @@ -77,7 +77,7 @@ func (rs Routes) Routes() chi.Router { * Intended for server-sided script playback. * But since we dont need that, we just use it for timestamps. */ -func (rs Routes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { +func (rs Routes) heresphereVideoEvent(w http.ResponseWriter, r *http.Request) { scn := r.Context().Value(sceneKey).(*models.Scene) var event HeresphereVideoEvent @@ -88,27 +88,25 @@ func (rs Routes) HeresphereVideoEvent(w http.ResponseWriter, r *http.Request) { return } - if event.Event == HeresphereEventClose { - newTime := event.Time / 1000 - newDuration := 0.0 - if newTime > scn.ResumeTime { - newDuration += (newTime - scn.ResumeTime) - } + newTime := event.Time / 1000 + newDuration := 0.0 + if newTime > scn.ResumeTime { + newDuration += (newTime - scn.ResumeTime) + } - if err := updatePlayCount(r.Context(), scn, event, rs.TxnManager, rs.Repository.Scene); err != nil { - logger.Errorf("Heresphere HeresphereVideoEvent updatePlayCount error: %s\n", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + if err := updatePlayCount(r.Context(), scn, event, rs.TxnManager, rs.Repository.Scene); err != nil { + logger.Errorf("Heresphere HeresphereVideoEvent updatePlayCount error: %s\n", err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } - if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { - _, err := rs.Repository.Scene.SaveActivity(ctx, scn.ID, &newTime, &newDuration) - return err - }); err != nil { - logger.Errorf("Heresphere HeresphereVideoEvent SaveActivity error: %s\n", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + if err := txn.WithTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + _, err := rs.Repository.Scene.SaveActivity(ctx, scn.ID, &newTime, &newDuration) + return err + }); err != nil { + logger.Errorf("Heresphere HeresphereVideoEvent SaveActivity error: %s\n", err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return } w.WriteHeader(http.StatusOK) @@ -178,7 +176,7 @@ func (rs Routes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Reques /* * This endpoint provides the main libraries that are available to browse. */ -func (rs Routes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { +func (rs Routes) heresphereIndex(w http.ResponseWriter, r *http.Request) { // Banner banner := HeresphereBanner{ Image: fmt.Sprintf("%s%s", manager.GetBaseURL(r), "/apple-touch-icon.png"), @@ -225,7 +223,7 @@ func (rs Routes) HeresphereIndex(w http.ResponseWriter, r *http.Request) { /* * This endpoint provides a single scenes full information. */ -func (rs Routes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { +func (rs Routes) heresphereVideoData(w http.ResponseWriter, r *http.Request) { user := r.Context().Value(authKey).(HeresphereAuthReq) c := config.GetInstance() @@ -322,7 +320,7 @@ func (rs Routes) HeresphereVideoData(w http.ResponseWriter, r *http.Request) { /* * This endpoint function allows the user to login and receive a token if successful. */ -func (rs Routes) HeresphereLoginToken(w http.ResponseWriter, r *http.Request) { +func (rs Routes) heresphereLoginToken(w http.ResponseWriter, r *http.Request) { user := r.Context().Value(authKey).(HeresphereAuthReq) // Try login @@ -358,7 +356,7 @@ func (rs Routes) HeresphereLoginToken(w http.ResponseWriter, r *http.Request) { /* * This context function finds the applicable scene from the request and stores it. */ -func (rs Routes) HeresphereSceneCtx(next http.Handler) http.Handler { +func (rs Routes) heresphereSceneCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get sceneId sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) @@ -399,7 +397,7 @@ func (rs Routes) HeresphereSceneCtx(next http.Handler) http.Handler { /* * This context function finds if the authentication is correct, otherwise rejects the request. */ -func (rs Routes) HeresphereCtx(next http.Handler) http.Handler { +func (rs Routes) heresphereCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Add JSON Header (using Add uses camel case and makes it invalid because "Json") w.Header()["HereSphere-JSON-Version"] = []string{strconv.Itoa(HeresphereJsonVersion)} diff --git a/internal/heresphere/update.go b/internal/heresphere/update.go index 7c176f64c76..e8f44ae5af0 100644 --- a/internal/heresphere/update.go +++ b/internal/heresphere/update.go @@ -27,11 +27,12 @@ func updatePlayCount(ctx context.Context, scn *models.Scene, event HeresphereVid newTime := event.Time / 1000 file := scn.Files.Primary() - if file != nil && newTime/file.Duration > float64(per)/100.0 { + // TODO: Need temporal memory, we need to track "Open" videos to do this properly + if scn.PlayCount == 0 && file != nil && newTime/file.Duration > float64(per)/100.0 { ret := &scene.UpdateSet{ - ID: scn.ID, + ID: scn.ID, + Partial: models.NewScenePartial(), } - ret.Partial = models.NewScenePartial() ret.Partial.PlayCount.Set = true ret.Partial.PlayCount.Value = scn.PlayCount + 1 From 45c5d1770ae38be2fe4a9a02a281d8ef81b6c31b Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Mon, 11 Sep 2023 13:59:45 +0200 Subject: [PATCH 127/144] Sort filter names --- internal/heresphere/routes.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 1481269f095..874bca43728 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net/http" + "sort" "strconv" "strings" @@ -193,7 +194,15 @@ func (rs Routes) heresphereIndex(w http.ResponseWriter, r *http.Request) { // Add filters parsedFilters, err := getAllFilters(r.Context(), rs.Repository) if err == nil { - for key, value := range parsedFilters { + var keys []string + for key := range parsedFilters { + keys = append(keys, key) + } + + sort.Strings(keys) + + for _, key := range keys { + value := parsedFilters[key] sceneUrls := make([]string, len(value)) for idx, sceneID := range value { From c4fe002466b57a0c45c26abb281f0b13e0e3390f Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 12 Sep 2023 15:24:40 +0200 Subject: [PATCH 128/144] Fix indent --- ui/v2.5/src/components/Settings/context.tsx | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ui/v2.5/src/components/Settings/context.tsx b/ui/v2.5/src/components/Settings/context.tsx index d79b20657a9..2db1723a094 100644 --- a/ui/v2.5/src/components/Settings/context.tsx +++ b/ui/v2.5/src/components/Settings/context.tsx @@ -395,19 +395,19 @@ export const SettingsContext: React.FC = ({ children }) => { // saves the configuration if no further changes are made after a half second const saveHSPConfig = useDebounce(async (input: GQL.ConfigHspInput) => { - try { - setUpdateSuccess(undefined); - await updateHSPConfig({ - variables: { - input, - }, - }); + try { + setUpdateSuccess(undefined); + await updateHSPConfig({ + variables: { + input, + }, + }); - setPendingHSP(undefined); - onSuccess(); - } catch (e) { - setSaveError(e); - } + setPendingHSP(undefined); + onSuccess(); + } catch (e) { + setSaveError(e); + } }, 500); useEffect(() => { From 0b98b466fc55b954c87755cc3a11fe435ab392fc Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 12 Sep 2023 15:55:46 +0200 Subject: [PATCH 129/144] Temporal storage for playcount --- internal/heresphere/routes.go | 17 +++++++++++++---- internal/heresphere/update.go | 13 +++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 874bca43728..83d6c89b97e 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -73,6 +73,10 @@ func (rs Routes) Routes() chi.Router { return r } +var ( + idMap = make(map[string]string) +) + /* * This is a video playback event * Intended for server-sided script playback. @@ -95,10 +99,15 @@ func (rs Routes) heresphereVideoEvent(w http.ResponseWriter, r *http.Request) { newDuration += (newTime - scn.ResumeTime) } - if err := updatePlayCount(r.Context(), scn, event, rs.TxnManager, rs.Repository.Scene); err != nil { - logger.Errorf("Heresphere HeresphereVideoEvent updatePlayCount error: %s\n", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + previousID := idMap[r.RemoteAddr] + if previousID != event.Id || event.Event == HeresphereEventClose { + if b, err := updatePlayCount(r.Context(), scn, event, rs.TxnManager, rs.Repository.Scene); err != nil { + logger.Errorf("Heresphere HeresphereVideoEvent updatePlayCount error: %s\n", err.Error()) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } else if b { + idMap[r.RemoteAddr] = event.Id + } } if err := txn.WithTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { diff --git a/internal/heresphere/update.go b/internal/heresphere/update.go index e8f44ae5af0..6d20270094e 100644 --- a/internal/heresphere/update.go +++ b/internal/heresphere/update.go @@ -22,13 +22,12 @@ func updateRating(user HeresphereAuthReq, ret *scene.UpdateSet) (bool, error) { /* * Modifies the scene PlayCount */ -func updatePlayCount(ctx context.Context, scn *models.Scene, event HeresphereVideoEvent, txnManager txn.Manager, fqb models.SceneReaderWriter) error { +func updatePlayCount(ctx context.Context, scn *models.Scene, event HeresphereVideoEvent, txnManager txn.Manager, fqb models.SceneReaderWriter) (bool, error) { if per, err := getMinPlayPercent(); err == nil { newTime := event.Time / 1000 file := scn.Files.Primary() - // TODO: Need temporal memory, we need to track "Open" videos to do this properly - if scn.PlayCount == 0 && file != nil && newTime/file.Duration > float64(per)/100.0 { + if file != nil && newTime/file.Duration > float64(per)/100.0 { ret := &scene.UpdateSet{ ID: scn.ID, Partial: models.NewScenePartial(), @@ -36,16 +35,14 @@ func updatePlayCount(ctx context.Context, scn *models.Scene, event HeresphereVid ret.Partial.PlayCount.Set = true ret.Partial.PlayCount.Value = scn.PlayCount + 1 - if err := txn.WithTxn(ctx, txnManager, func(ctx context.Context) error { + return true, txn.WithTxn(ctx, txnManager, func(ctx context.Context) error { _, err := ret.Update(ctx, fqb) return err - }); err != nil { - return err - } + }) } } - return nil + return false, nil } /* From 60c3fc54f017ae0474f8c0b4565a78adf5769751 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 12 Sep 2023 16:03:29 +0200 Subject: [PATCH 130/144] Fix temoral storage var --- internal/heresphere/update.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/heresphere/update.go b/internal/heresphere/update.go index 6d20270094e..7061db236eb 100644 --- a/internal/heresphere/update.go +++ b/internal/heresphere/update.go @@ -35,10 +35,11 @@ func updatePlayCount(ctx context.Context, scn *models.Scene, event HeresphereVid ret.Partial.PlayCount.Set = true ret.Partial.PlayCount.Value = scn.PlayCount + 1 - return true, txn.WithTxn(ctx, txnManager, func(ctx context.Context) error { + err := txn.WithTxn(ctx, txnManager, func(ctx context.Context) error { _, err := ret.Update(ctx, fqb) return err }) + return err == nil, err } } From cef47250f4f6a2388041ef79b7a5e92293c03070 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Sun, 17 Sep 2023 01:50:19 +0200 Subject: [PATCH 131/144] Small tags update --- internal/heresphere/tags_read.go | 143 ++++++++++++------------------ internal/heresphere/tags_write.go | 10 ++- 2 files changed, 65 insertions(+), 88 deletions(-) diff --git a/internal/heresphere/tags_read.go b/internal/heresphere/tags_read.go index 45cb68dc4c6..55dde9ebdea 100644 --- a/internal/heresphere/tags_read.go +++ b/internal/heresphere/tags_read.go @@ -63,12 +63,11 @@ func generateMarkerTags(ctx context.Context, rs Routes, scene *models.Scene) []H } } - genTag := HeresphereVideoTag{ + tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("Marker:%s", tagName), Start: mark.Seconds * 1000, End: (mark.Seconds + 60) * 1000, - } - tags = append(tags, genTag) + }) } return tags @@ -84,10 +83,9 @@ func generateTagTags(ctx context.Context, rs Routes, scene *models.Scene) []Here } for _, tag := range tagIDs { - genTag := HeresphereVideoTag{ + tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("Tag:%s", tag.Name), - } - tags = append(tags, genTag) + }) } return tags @@ -104,10 +102,12 @@ func generatePerformerTags(ctx context.Context, rs Routes, scene *models.Scene) } for _, perf := range perfIDs { - genTag := HeresphereVideoTag{ + tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("Performer:%s", perf.Name), - } - tags = append(tags, genTag) + }) + tags = append(tags, HeresphereVideoTag{ + Name: fmt.Sprintf("HasFavoritedPerformer:%s", strconv.FormatBool(perf.Favorite)), + }) } return tags @@ -125,10 +125,9 @@ func generateGalleryTags(ctx context.Context, rs Routes, scene *models.Scene) [] } for _, gallery := range galleries { - genTag := HeresphereVideoTag{ + tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("Gallery:%s", gallery.Title), - } - tags = append(tags, genTag) + }) } } @@ -153,10 +152,9 @@ func generateMovieTags(ctx context.Context, rs Routes, scene *models.Scene) []He } for _, movie := range movies { - genTag := HeresphereVideoTag{ + tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("Movie:%s", movie.Name), - } - tags = append(tags, genTag) + }) } } @@ -174,10 +172,9 @@ func generateStudioTag(ctx context.Context, rs Routes, scene *models.Scene) []He return tags } - genTag := HeresphereVideoTag{ + tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("Studio:%s", studio.Name), - } - tags = append(tags, genTag) + }) } return tags @@ -189,25 +186,23 @@ func generateInteractiveTag(scene *models.Scene) []HeresphereVideoTag { primaryFile := scene.Files.Primary() if primaryFile != nil { - genTag := HeresphereVideoTag{ + tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("%s:%s", string(HeresphereCustomTagInteractive), strconv.FormatBool(primaryFile.Interactive), ), - } - tags = append(tags, genTag) + }) if primaryFile.Interactive { funSpeed := 0 if primaryFile.InteractiveSpeed != nil { funSpeed = *primaryFile.InteractiveSpeed } - genTag := HeresphereVideoTag{ + tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("Funspeed:%d", funSpeed, ), - } - tags = append(tags, genTag) + }) } } @@ -219,10 +214,9 @@ func generateDirectorTag(scene *models.Scene) []HeresphereVideoTag { tags := []HeresphereVideoTag{} if len(scene.Director) > 0 { - genTag := HeresphereVideoTag{ + tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("Director:%s", scene.Director), - } - tags = append(tags, genTag) + }) } return tags @@ -233,12 +227,11 @@ func generateRatingTag(scene *models.Scene) []HeresphereVideoTag { tags := []HeresphereVideoTag{} if scene.Rating != nil { - genTag := HeresphereVideoTag{ + tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("Rating:%d", models.Rating100To5(*scene.Rating), ), - } - tags = append(tags, genTag) + }) } return tags @@ -246,82 +239,64 @@ func generateRatingTag(scene *models.Scene) []HeresphereVideoTag { func generateWatchedTag(scene *models.Scene) []HeresphereVideoTag { // Generate watched tag - tags := []HeresphereVideoTag{} - - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagWatched), - strconv.FormatBool(scene.PlayCount > 0), - ), + return []HeresphereVideoTag{ + { + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagWatched), + strconv.FormatBool(scene.PlayCount > 0), + ), + }, } - tags = append(tags, genTag) - - return tags } func generateOrganizedTag(scene *models.Scene) []HeresphereVideoTag { // Generate organized tag - tags := []HeresphereVideoTag{} - - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagOrganized), - strconv.FormatBool(scene.Organized), - ), + return []HeresphereVideoTag{ + { + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagOrganized), + strconv.FormatBool(scene.Organized), + ), + }, } - tags = append(tags, genTag) - - return tags } func generateRatedTag(scene *models.Scene) []HeresphereVideoTag { // Generate rated tag - tags := []HeresphereVideoTag{} - - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagRated), - strconv.FormatBool(scene.Rating != nil), - ), + return []HeresphereVideoTag{ + { + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagRated), + strconv.FormatBool(scene.Rating != nil), + ), + }, } - tags = append(tags, genTag) - - return tags } func generateOrgasmedTag(scene *models.Scene) []HeresphereVideoTag { // Generate orgasmed tag - tags := []HeresphereVideoTag{} - - genTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%s", - string(HeresphereCustomTagOrgasmed), - strconv.FormatBool(scene.OCounter > 0), - ), + return []HeresphereVideoTag{ + { + Name: fmt.Sprintf("%s:%s", + string(HeresphereCustomTagOrgasmed), + strconv.FormatBool(scene.OCounter > 0), + ), + }, } - tags = append(tags, genTag) - - return tags } func generatePlayCountTag(scene *models.Scene) []HeresphereVideoTag { - tags := []HeresphereVideoTag{} - - playCountTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagPlayCount), scene.PlayCount), + return []HeresphereVideoTag{ + { + Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagPlayCount), scene.PlayCount), + }, } - tags = append(tags, playCountTag) - - return tags } func generateOCounterTag(scene *models.Scene) []HeresphereVideoTag { - tags := []HeresphereVideoTag{} - - oCounterTag := HeresphereVideoTag{ - Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagOCounter), scene.OCounter), + return []HeresphereVideoTag{ + { + Name: fmt.Sprintf("%s:%d", string(HeresphereCustomTagOCounter), scene.OCounter), + }, } - tags = append(tags, oCounterTag) - - return tags } diff --git a/internal/heresphere/tags_write.go b/internal/heresphere/tags_write.go index 948e75ccc24..567a51c0a57 100644 --- a/internal/heresphere/tags_write.go +++ b/internal/heresphere/tags_write.go @@ -150,8 +150,8 @@ func handleAddMarker(ctx context.Context, rs Routes, tag HeresphereVideoTag, sce // Search for tag if markers, err := rs.Repository.SceneMarker.FindBySceneID(ctx, scene.ID); err == nil { - i, e := strconv.Atoi(*tagId) - if e == nil { + i, err := strconv.Atoi(*tagId) + if err == nil { // Note: Currently we search if a marker exists. // If it doesn't, create it. // This also means that markers CANNOT be deleted using the api. @@ -167,7 +167,7 @@ func handleAddMarker(ctx context.Context, rs Routes, tag HeresphereVideoTag, sce } return err - }); tagId != nil { + }); err != nil || tagId != nil { // Create marker i, e := strconv.Atoi(*tagId) if e == nil { @@ -181,7 +181,9 @@ func handleAddMarker(ctx context.Context, rs Routes, tag HeresphereVideoTag, sce UpdatedAt: currentTime, } - if rs.Repository.SceneMarker.Create(ctx, &newMarker) != nil { + if err := txn.WithTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + return rs.Repository.SceneMarker.Create(ctx, &newMarker) + }); err != nil { logger.Errorf("Heresphere handleTags SceneMarker.Create error: %s\n", err.Error()) } } From 1e53c083b86ed087a02eecfaa0fbd6f7c0c236bb Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Sun, 17 Sep 2023 01:52:41 +0200 Subject: [PATCH 132/144] Small logic fix from prev commit --- internal/heresphere/tags_read.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/heresphere/tags_read.go b/internal/heresphere/tags_read.go index 55dde9ebdea..5f22052a591 100644 --- a/internal/heresphere/tags_read.go +++ b/internal/heresphere/tags_read.go @@ -101,15 +101,18 @@ func generatePerformerTags(ctx context.Context, rs Routes, scene *models.Scene) return tags } + hasFavPerformer := false for _, perf := range perfIDs { tags = append(tags, HeresphereVideoTag{ Name: fmt.Sprintf("Performer:%s", perf.Name), }) - tags = append(tags, HeresphereVideoTag{ - Name: fmt.Sprintf("HasFavoritedPerformer:%s", strconv.FormatBool(perf.Favorite)), - }) + hasFavPerformer = hasFavPerformer || perf.Favorite } + tags = append(tags, HeresphereVideoTag{ + Name: fmt.Sprintf("HasFavoritedPerformer:%s", strconv.FormatBool(hasFavPerformer)), + }) + return tags } From e747272be590dd23f4bfd26c395cb2dd58ad151e Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:10:50 +0200 Subject: [PATCH 133/144] Detect skip events when adding duration --- internal/heresphere/routes.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 83d6c89b97e..0bd9cc60be4 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -83,25 +83,35 @@ var ( * But since we dont need that, we just use it for timestamps. */ func (rs Routes) heresphereVideoEvent(w http.ResponseWriter, r *http.Request) { + // Get the scene from the request context scn := r.Context().Value(sceneKey).(*models.Scene) + // Decode the JSON request body into the HeresphereVideoEvent struct var event HeresphereVideoEvent err := json.NewDecoder(r.Body).Decode(&event) if err != nil { + // Handle JSON decoding error logger.Errorf("Heresphere HeresphereVideoEvent decode error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusBadRequest) return } + // Convert time from milliseconds to seconds newTime := event.Time / 1000 newDuration := 0.0 - if newTime > scn.ResumeTime { + + // Calculate new duration if necessary + // (if HeresphereEventPlay then its most likely a "skip" event) + if newTime > scn.ResumeTime && event.Event != HeresphereEventPlay { newDuration += (newTime - scn.ResumeTime) } + // Check if the event ID is different from the previous event for the same client previousID := idMap[r.RemoteAddr] - if previousID != event.Id || event.Event == HeresphereEventClose { + if previousID != event.Id { + // Update play count and store the new event ID if needed if b, err := updatePlayCount(r.Context(), scn, event, rs.TxnManager, rs.Repository.Scene); err != nil { + // Handle updatePlayCount error logger.Errorf("Heresphere HeresphereVideoEvent updatePlayCount error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -110,15 +120,18 @@ func (rs Routes) heresphereVideoEvent(w http.ResponseWriter, r *http.Request) { } } + // Update the scene activity with the new time and duration if err := txn.WithTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { _, err := rs.Repository.Scene.SaveActivity(ctx, scn.ID, &newTime, &newDuration) return err }); err != nil { + // Handle SaveActivity error logger.Errorf("Heresphere HeresphereVideoEvent SaveActivity error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } + // Respond with a successful HTTP status code w.WriteHeader(http.StatusOK) } From 4298fd409d6dac963ae5ab0128d9f44624f6eae3 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:05:36 +0200 Subject: [PATCH 134/144] Fixed bug where favoriting would delete tags --- internal/heresphere/favorite.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/internal/heresphere/favorite.go b/internal/heresphere/favorite.go index a48b926bc8c..b53f7f2cb2d 100644 --- a/internal/heresphere/favorite.go +++ b/internal/heresphere/favorite.go @@ -40,18 +40,14 @@ func handleFavoriteTag(ctx context.Context, rs Routes, scn *models.Scene, user * favTagVal := HeresphereVideoTag{Name: fmt.Sprintf("Tag:%s", favTag.Name)} + if user.Tags == nil { + sceneTags := getVideoTags(ctx, rs, scn) + user.Tags = &sceneTags + } + if *user.IsFavorite { - if user.Tags == nil { - user.Tags = &[]HeresphereVideoTag{favTagVal} - } else { - *user.Tags = append(*user.Tags, favTagVal) - } + *user.Tags = append(*user.Tags, favTagVal) } else { - if user.Tags == nil { - sceneTags := getVideoTags(ctx, rs, scn) - user.Tags = &sceneTags - } - for i, tag := range *user.Tags { if tag.Name == favTagVal.Name { *user.Tags = append((*user.Tags)[:i], (*user.Tags)[i+1:]...) From 2287dd69c6bb000e08e88b3f8fed568b883d75fa Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:06:19 +0200 Subject: [PATCH 135/144] Fixed MovieIDs == nil bug --- internal/heresphere/tags_write.go | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/internal/heresphere/tags_write.go b/internal/heresphere/tags_write.go index 567a51c0a57..23ac22610e3 100644 --- a/internal/heresphere/tags_write.go +++ b/internal/heresphere/tags_write.go @@ -38,9 +38,6 @@ func handleTags(ctx context.Context, scn *models.Scene, user *HeresphereAuthReq, if handleAddMarker(ctx, rs, tagI, scn) { continue } - if handleAddMovie(ctx, rs, tagI, scn, ret) { - continue - } if handleAddStudio(ctx, rs, tagI, scn, ret) { continue } @@ -191,29 +188,6 @@ func handleAddMarker(ctx context.Context, rs Routes, tag HeresphereVideoTag, sce return true } -func handleAddMovie(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { - if !strings.HasPrefix(tag.Name, "Movie:") { - return false - } - - after := strings.TrimPrefix(tag.Name, "Movie:") - - var err error - var tagMod *models.Movie - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { - // Search for performer - tagMod, err = rs.Repository.Movie.FindByName(ctx, after, true) - return err - }); err == nil { - ret.Partial.MovieIDs.Mode = models.RelationshipUpdateModeSet - ret.Partial.MovieIDs.AddUnique(models.MoviesScenes{ - MovieID: tagMod.ID, - SceneIndex: &scene.ID, - }) - } - - return true -} func handleAddStudio(ctx context.Context, rs Routes, tag HeresphereVideoTag, scene *models.Scene, ret *scene.UpdateSet) bool { if !strings.HasPrefix(tag.Name, "Studio:") { return false From e68ee8a2765e60851f52be703ba9f391d006d7e5 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:44:39 +0200 Subject: [PATCH 136/144] Fix small logic error in tags_write --- internal/heresphere/tags_write.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/heresphere/tags_write.go b/internal/heresphere/tags_write.go index 23ac22610e3..7460774cb2e 100644 --- a/internal/heresphere/tags_write.go +++ b/internal/heresphere/tags_write.go @@ -276,7 +276,7 @@ func handleSetPlayCount(ctx context.Context, rs Routes, tag HeresphereVideoTag, } after := strings.TrimPrefix(tag.Name, prefix) - if numRes, err := strconv.Atoi(after); err != nil { + if numRes, err := strconv.Atoi(after); err == nil { ret.Partial.PlayCount.Set = true ret.Partial.PlayCount.Value = numRes } @@ -290,7 +290,7 @@ func handleSetOCount(ctx context.Context, rs Routes, tag HeresphereVideoTag, sce } after := strings.TrimPrefix(tag.Name, prefix) - if numRes, err := strconv.Atoi(after); err != nil { + if numRes, err := strconv.Atoi(after); err == nil { ret.Partial.OCounter.Set = true ret.Partial.OCounter.Value = numRes } From d6f6fe987029c3175a1556fdcf6d2a47df86c178 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 18 Oct 2023 11:51:42 +0200 Subject: [PATCH 137/144] Compliance update --- internal/api/routes.go | 15 +++++++++++++++ internal/api/routes_image.go | 6 +++--- internal/api/routes_movie.go | 10 +++++----- internal/api/routes_performer.go | 8 ++++---- internal/api/routes_scene.go | 22 +++++++++++----------- internal/api/routes_studio.go | 8 ++++---- internal/api/routes_tag.go | 8 ++++---- internal/heresphere/favorite.go | 5 ++--- internal/heresphere/filter_structs.go | 2 -- internal/heresphere/filters.go | 9 ++++----- internal/heresphere/interfaces.go | 17 ++++++++++++++++- internal/heresphere/media.go | 2 +- internal/heresphere/routes.go | 19 +++++++++---------- internal/heresphere/tags_read.go | 3 +-- internal/heresphere/tags_write.go | 11 +++++------ internal/heresphere/update.go | 5 ++--- pkg/models/rating.go | 2 +- pkg/models/routes.go | 15 --------------- 18 files changed, 87 insertions(+), 80 deletions(-) create mode 100644 internal/api/routes.go delete mode 100644 pkg/models/routes.go diff --git a/internal/api/routes.go b/internal/api/routes.go new file mode 100644 index 00000000000..e3a0f48c083 --- /dev/null +++ b/internal/api/routes.go @@ -0,0 +1,15 @@ +package api + +import ( + "net/http" + + "github.com/stashapp/stash/pkg/txn" +) + +type routes struct { + txnManager txn.Manager +} + +func (rs routes) withReadTxn(r *http.Request, fn txn.TxnFunc) error { + return txn.WithReadTxn(r.Context(), rs.txnManager, fn) +} diff --git a/internal/api/routes_image.go b/internal/api/routes_image.go index 4a267134b1c..9c58bc72d8f 100644 --- a/internal/api/routes_image.go +++ b/internal/api/routes_image.go @@ -25,14 +25,14 @@ type ImageFinder interface { } type imageRoutes struct { - models.TxnRoutes + routes imageFinder ImageFinder fileGetter models.FileGetter } func getImageRoutes(repo models.Repository) chi.Router { return imageRoutes{ - TxnRoutes: models.TxnRoutes{TxnManager: repo.TxnManager}, + routes: routes{txnManager: repo.TxnManager}, imageFinder: repo.Image, fileGetter: repo.File, }.Routes() @@ -154,7 +154,7 @@ func (rs imageRoutes) ImageCtx(next http.Handler) http.Handler { imageID, _ := strconv.Atoi(imageIdentifierQueryParam) var image *models.Image - _ = rs.WithReadTxn(r, func(ctx context.Context) error { + _ = rs.withReadTxn(r, func(ctx context.Context) error { qb := rs.imageFinder if imageID == 0 { images, _ := qb.FindByChecksum(ctx, imageIdentifierQueryParam) diff --git a/internal/api/routes_movie.go b/internal/api/routes_movie.go index 090b0362a87..7487755cfaa 100644 --- a/internal/api/routes_movie.go +++ b/internal/api/routes_movie.go @@ -21,13 +21,13 @@ type MovieFinder interface { } type movieRoutes struct { - models.TxnRoutes + routes movieFinder MovieFinder } func getMovieRoutes(repo models.Repository) chi.Router { return movieRoutes{ - TxnRoutes: models.TxnRoutes{TxnManager: repo.TxnManager}, + routes: routes{txnManager: repo.TxnManager}, movieFinder: repo.Movie, }.Routes() } @@ -49,7 +49,7 @@ func (rs movieRoutes) FrontImage(w http.ResponseWriter, r *http.Request) { defaultParam := r.URL.Query().Get("default") var image []byte if defaultParam != "true" { - readTxnErr := rs.WithReadTxn(r, func(ctx context.Context) error { + readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error { var err error image, err = rs.movieFinder.GetFrontImage(ctx, movie.ID) return err @@ -75,7 +75,7 @@ func (rs movieRoutes) BackImage(w http.ResponseWriter, r *http.Request) { defaultParam := r.URL.Query().Get("default") var image []byte if defaultParam != "true" { - readTxnErr := rs.WithReadTxn(r, func(ctx context.Context) error { + readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error { var err error image, err = rs.movieFinder.GetBackImage(ctx, movie.ID) return err @@ -105,7 +105,7 @@ func (rs movieRoutes) MovieCtx(next http.Handler) http.Handler { } var movie *models.Movie - _ = rs.WithReadTxn(r, func(ctx context.Context) error { + _ = rs.withReadTxn(r, func(ctx context.Context) error { movie, _ = rs.movieFinder.Find(ctx, movieID) return nil }) diff --git a/internal/api/routes_performer.go b/internal/api/routes_performer.go index 509770ba239..8ac4ee34901 100644 --- a/internal/api/routes_performer.go +++ b/internal/api/routes_performer.go @@ -18,13 +18,13 @@ type PerformerFinder interface { } type performerRoutes struct { - models.TxnRoutes + routes performerFinder PerformerFinder } func getPerformerRoutes(repo models.Repository) chi.Router { return performerRoutes{ - TxnRoutes: models.TxnRoutes{TxnManager: repo.TxnManager}, + routes: routes{txnManager: repo.TxnManager}, performerFinder: repo.Performer, }.Routes() } @@ -46,7 +46,7 @@ func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) { var image []byte if defaultParam != "true" { - readTxnErr := rs.WithReadTxn(r, func(ctx context.Context) error { + readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error { var err error image, err = rs.performerFinder.GetImage(ctx, performer.ID) return err @@ -75,7 +75,7 @@ func (rs performerRoutes) PerformerCtx(next http.Handler) http.Handler { } var performer *models.Performer - _ = rs.WithReadTxn(r, func(ctx context.Context) error { + _ = rs.withReadTxn(r, func(ctx context.Context) error { var err error performer, err = rs.performerFinder.Find(ctx, performerID) return err diff --git a/internal/api/routes_scene.go b/internal/api/routes_scene.go index 5953e743826..3bbc5b258ce 100644 --- a/internal/api/routes_scene.go +++ b/internal/api/routes_scene.go @@ -42,7 +42,7 @@ type CaptionFinder interface { } type sceneRoutes struct { - models.TxnRoutes + routes sceneFinder SceneFinder fileGetter models.FileGetter captionFinder CaptionFinder @@ -52,7 +52,7 @@ type sceneRoutes struct { func getSceneRoutes(repo models.Repository) chi.Router { return sceneRoutes{ - TxnRoutes: models.TxnRoutes{TxnManager: repo.TxnManager}, + routes: routes{txnManager: repo.TxnManager}, sceneFinder: repo.Scene, fileGetter: repo.File, captionFinder: repo.File, @@ -102,7 +102,7 @@ func (rs sceneRoutes) Routes() chi.Router { func (rs sceneRoutes) StreamDirect(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) ss := manager.SceneServer{ - TxnManager: rs.TxnManager, + TxnManager: rs.txnManager, SceneCoverGetter: rs.sceneFinder, } ss.StreamSceneDirect(scene, w, r) @@ -256,7 +256,7 @@ func (rs sceneRoutes) Screenshot(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) ss := manager.SceneServer{ - TxnManager: rs.TxnManager, + TxnManager: rs.txnManager, SceneCoverGetter: rs.sceneFinder, } ss.ServeScreenshot(scene, w, r) @@ -284,7 +284,7 @@ func (rs sceneRoutes) getChapterVttTitle(r *http.Request, marker *models.SceneMa } var title string - if err := rs.WithReadTxn(r, func(ctx context.Context) error { + if err := rs.withReadTxn(r, func(ctx context.Context) error { qb := rs.tagFinder primaryTag, err := qb.Find(ctx, marker.PrimaryTagID) if err != nil { @@ -313,7 +313,7 @@ func (rs sceneRoutes) getChapterVttTitle(r *http.Request, marker *models.SceneMa func (rs sceneRoutes) VttChapter(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) var sceneMarkers []*models.SceneMarker - readTxnErr := rs.WithReadTxn(r, func(ctx context.Context) error { + readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error { var err error sceneMarkers, err = rs.sceneMarkerFinder.FindBySceneID(ctx, scene.ID) return err @@ -412,7 +412,7 @@ func (rs sceneRoutes) Caption(w http.ResponseWriter, r *http.Request, lang strin s := r.Context().Value(sceneKey).(*models.Scene) var captions []*models.VideoCaption - readTxnErr := rs.WithReadTxn(r, func(ctx context.Context) error { + readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error { var err error primaryFile := s.Files.Primary() if primaryFile == nil { @@ -474,7 +474,7 @@ func (rs sceneRoutes) SceneMarkerStream(w http.ResponseWriter, r *http.Request) sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId")) var sceneMarker *models.SceneMarker - readTxnErr := rs.WithReadTxn(r, func(ctx context.Context) error { + readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error { var err error sceneMarker, err = rs.sceneMarkerFinder.Find(ctx, sceneMarkerID) return err @@ -502,7 +502,7 @@ func (rs sceneRoutes) SceneMarkerPreview(w http.ResponseWriter, r *http.Request) sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId")) var sceneMarker *models.SceneMarker - readTxnErr := rs.WithReadTxn(r, func(ctx context.Context) error { + readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error { var err error sceneMarker, err = rs.sceneMarkerFinder.Find(ctx, sceneMarkerID) return err @@ -538,7 +538,7 @@ func (rs sceneRoutes) SceneMarkerScreenshot(w http.ResponseWriter, r *http.Reque sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId")) var sceneMarker *models.SceneMarker - readTxnErr := rs.WithReadTxn(r, func(ctx context.Context) error { + readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error { var err error sceneMarker, err = rs.sceneMarkerFinder.Find(ctx, sceneMarkerID) return err @@ -578,7 +578,7 @@ func (rs sceneRoutes) SceneCtx(next http.Handler) http.Handler { } var scene *models.Scene - _ = rs.WithReadTxn(r, func(ctx context.Context) error { + _ = rs.withReadTxn(r, func(ctx context.Context) error { qb := rs.sceneFinder scene, _ = qb.Find(ctx, sceneID) diff --git a/internal/api/routes_studio.go b/internal/api/routes_studio.go index 507ca096eef..d61a1e7548d 100644 --- a/internal/api/routes_studio.go +++ b/internal/api/routes_studio.go @@ -19,13 +19,13 @@ type StudioFinder interface { } type studioRoutes struct { - models.TxnRoutes + routes studioFinder StudioFinder } func getStudioRoutes(repo models.Repository) chi.Router { return studioRoutes{ - TxnRoutes: models.TxnRoutes{TxnManager: repo.TxnManager}, + routes: routes{txnManager: repo.TxnManager}, studioFinder: repo.Studio, }.Routes() } @@ -47,7 +47,7 @@ func (rs studioRoutes) Image(w http.ResponseWriter, r *http.Request) { var image []byte if defaultParam != "true" { - readTxnErr := rs.WithReadTxn(r, func(ctx context.Context) error { + readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error { var err error image, err = rs.studioFinder.GetImage(ctx, studio.ID) return err @@ -77,7 +77,7 @@ func (rs studioRoutes) StudioCtx(next http.Handler) http.Handler { } var studio *models.Studio - _ = rs.WithReadTxn(r, func(ctx context.Context) error { + _ = rs.withReadTxn(r, func(ctx context.Context) error { var err error studio, err = rs.studioFinder.Find(ctx, studioID) return err diff --git a/internal/api/routes_tag.go b/internal/api/routes_tag.go index e2452b17b12..5c05ac07fb1 100644 --- a/internal/api/routes_tag.go +++ b/internal/api/routes_tag.go @@ -19,13 +19,13 @@ type TagFinder interface { } type tagRoutes struct { - models.TxnRoutes + routes tagFinder TagFinder } func getTagRoutes(repo models.Repository) chi.Router { return tagRoutes{ - TxnRoutes: models.TxnRoutes{TxnManager: repo.TxnManager}, + routes: routes{txnManager: repo.TxnManager}, tagFinder: repo.Tag, }.Routes() } @@ -47,7 +47,7 @@ func (rs tagRoutes) Image(w http.ResponseWriter, r *http.Request) { var image []byte if defaultParam != "true" { - readTxnErr := rs.WithReadTxn(r, func(ctx context.Context) error { + readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error { var err error image, err = rs.tagFinder.GetImage(ctx, tag.ID) return err @@ -77,7 +77,7 @@ func (rs tagRoutes) TagCtx(next http.Handler) http.Handler { } var tag *models.Tag - _ = rs.WithReadTxn(r, func(ctx context.Context) error { + _ = rs.withReadTxn(r, func(ctx context.Context) error { var err error tag, err = rs.tagFinder.Find(ctx, tagID) return err diff --git a/internal/heresphere/favorite.go b/internal/heresphere/favorite.go index ee757dc4bb8..83390508af2 100644 --- a/internal/heresphere/favorite.go +++ b/internal/heresphere/favorite.go @@ -9,7 +9,6 @@ import ( "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/txn" ) /* @@ -22,7 +21,7 @@ func (rs routes) handleFavoriteTag(ctx context.Context, scn *models.Scene, user favTag, err := func() (*models.Tag, error) { var tag *models.Tag var err error - err = txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + err = rs.withReadTxn(ctx, func(ctx context.Context) error { tag, err = rs.TagFinder.Find(ctx, tagID) return err }) @@ -66,7 +65,7 @@ func (rs routes) getVideoFavorite(r *http.Request, scene *models.Scene) bool { tagIDs, err := func() ([]*models.Tag, error) { var tags []*models.Tag var err error - err = txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + err = rs.withReadTxn(r.Context(), func(ctx context.Context) error { tags, err = rs.TagFinder.FindBySceneID(ctx, scene.ID) return err }) diff --git a/internal/heresphere/filter_structs.go b/internal/heresphere/filter_structs.go index 5db4eb2db27..61067284594 100644 --- a/internal/heresphere/filter_structs.go +++ b/internal/heresphere/filter_structs.go @@ -107,8 +107,6 @@ type SceneFilterTypeStored struct { Path *models.StringCriterionInput `json:"path"` // Filter by file count FileCount *IntCriterionStored `json:"file_count"` - // Filter by rating expressed as 1-5 - Rating *IntCriterionStored `json:"rating"` // Filter by rating expressed as 1-100 Rating100 *IntCriterionStored `json:"rating100"` // Filter by organized diff --git a/internal/heresphere/filters.go b/internal/heresphere/filters.go index 44621b08651..b6332c7c3e2 100644 --- a/internal/heresphere/filters.go +++ b/internal/heresphere/filters.go @@ -7,7 +7,6 @@ import ( "github.com/mitchellh/mapstructure" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/txn" ) func parseObjectFilter(sf *models.SavedFilter) (*models.SceneFilterType, error) { @@ -21,11 +20,11 @@ func parseObjectFilter(sf *models.SavedFilter) (*models.SceneFilterType, error) WeaklyTypedInput: true, }) if err != nil { - return nil, fmt.Errorf("Error creating decoder: %s", err) + return nil, fmt.Errorf("error creating decoder: %s", err) } if err := decoder.Decode(sf.ObjectFilter); err != nil { - return nil, fmt.Errorf("Error decoding map to struct: %s", err) + return nil, fmt.Errorf("error decoding map to struct: %s", err) } return result.ToOriginal(), nil @@ -36,7 +35,7 @@ func (rs routes) getAllFilters(ctx context.Context) (scenesMap map[string][]int, savedfilters, err := func() ([]*models.SavedFilter, error) { var filters []*models.SavedFilter - err = txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + err = rs.withReadTxn(ctx, func(ctx context.Context) error { filters, err = rs.FilterFinder.FindByMode(ctx, models.FilterModeScenes) return err }) @@ -78,7 +77,7 @@ func (rs routes) getAllFilters(ctx context.Context) (scenesMap map[string][]int, } var scenes *models.SceneQueryResult - err = txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + err = rs.withReadTxn(ctx, func(ctx context.Context) error { var err error scenes, err = rs.SceneFinder.Query(ctx, models.SceneQueryOptions{ QueryOptions: models.QueryOptions{ diff --git a/internal/heresphere/interfaces.go b/internal/heresphere/interfaces.go index 0dfa41861f2..e14b64cde2d 100644 --- a/internal/heresphere/interfaces.go +++ b/internal/heresphere/interfaces.go @@ -1,11 +1,26 @@ package heresphere import ( + "context" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/txn" ) +// Repository provides access to storage methods for files and folders. +type Repository struct { + TxnManager models.TxnManager +} + +func (r *Repository) withTxn(ctx context.Context, fn txn.TxnFunc) error { + return txn.WithTxn(ctx, r.TxnManager, fn) +} + +func (r *Repository) withReadTxn(ctx context.Context, fn txn.TxnFunc) error { + return txn.WithReadTxn(ctx, r.TxnManager, fn) +} + type sceneFinder interface { - models.SceneQueryer models.SceneGetter models.SceneReader models.SceneWriter diff --git a/internal/heresphere/media.go b/internal/heresphere/media.go index 78fac4611a3..297ba1d36b0 100644 --- a/internal/heresphere/media.go +++ b/internal/heresphere/media.go @@ -65,7 +65,7 @@ func (rs routes) getVideoSubtitles(r *http.Request, scene *models.Scene) []Heres captions, err := func() ([]*models.VideoCaption, error) { var captions []*models.VideoCaption var err error - err = txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + err = rs.withReadTxn(r.Context(), func(ctx context.Context) error { captions, err = rs.FileFinder.GetCaptions(ctx, primaryFile.ID) return err }) diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 66fe3b1cef7..a792b55c53d 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -20,7 +20,6 @@ import ( "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/txn" ) type HeresphereCustomTag string @@ -40,7 +39,7 @@ const ( ) type routes struct { - models.TxnRoutes + Repository SceneFinder sceneFinder SceneMarkerFinder sceneMarkerFinder FileFinder fileFinder @@ -54,7 +53,7 @@ type routes struct { func GetRoutes(repo models.Repository) chi.Router { return routes{ - TxnRoutes: models.TxnRoutes{TxnManager: repo.TxnManager}, + Repository: Repository{TxnManager: repo.TxnManager}, SceneFinder: repo.Scene, SceneMarkerFinder: repo.SceneMarker, FileFinder: repo.File, @@ -142,7 +141,7 @@ func (rs routes) heresphereVideoEvent(w http.ResponseWriter, r *http.Request) { } // Update the scene activity with the new time and duration - if err := txn.WithTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + if err := rs.withTxn(r.Context(), func(ctx context.Context) error { _, err := rs.SceneFinder.SaveActivity(ctx, scn.ID, &newTime, &newDuration) return err }); err != nil { @@ -159,7 +158,7 @@ func (rs routes) heresphereVideoEvent(w http.ResponseWriter, r *http.Request) { /* * This endpoint is for letting the user update scene data */ -func (rs routes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) error { +func (rs routes) heresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) error { scn := r.Context().Value(sceneKey).(*models.Scene) user := r.Context().Value(authKey).(HeresphereAuthReq) fileDeleter := file.NewDeleter() @@ -204,7 +203,7 @@ func (rs routes) HeresphereVideoDataUpdate(w http.ResponseWriter, r *http.Reques } if shouldUpdate { - if err := txn.WithTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + if err := rs.withTxn(r.Context(), func(ctx context.Context) error { _, err := ret.Update(ctx, rs.SceneFinder) return err }); err != nil { @@ -280,7 +279,7 @@ func (rs routes) heresphereVideoData(w http.ResponseWriter, r *http.Request) { c := config.GetInstance() // Update request - if err := rs.HeresphereVideoDataUpdate(w, r); err != nil { + if err := rs.heresphereVideoDataUpdate(w, r); err != nil { logger.Errorf("Heresphere HeresphereVideoData HeresphereVideoDataUpdate error: %s\n", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -291,7 +290,7 @@ func (rs routes) heresphereVideoData(w http.ResponseWriter, r *http.Request) { // Load relationships processedScene := HeresphereVideoEntry{} - if err := txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + if err := rs.withReadTxn(r.Context(), func(ctx context.Context) error { return scene.LoadRelationships(ctx, rs.SceneFinder) }); err != nil { logger.Errorf("Heresphere HeresphereVideoData LoadRelationships error: %s\n", err.Error()) @@ -307,7 +306,7 @@ func (rs routes) heresphereVideoData(w http.ResponseWriter, r *http.Request) { ThumbnailImage: addApiKey(urlbuilders.NewSceneURLBuilder(manager.GetBaseURL(r), scene).GetScreenshotURL()), ThumbnailVideo: addApiKey(urlbuilders.NewSceneURLBuilder(manager.GetBaseURL(r), scene).GetStreamPreviewURL()), DateAdded: scene.CreatedAt.Format("2006-01-02"), - Duration: 60000.0, + Duration: 0.0, Rating: 0, Favorites: 0, Comments: scene.OCounter, @@ -419,7 +418,7 @@ func (rs routes) heresphereSceneCtx(next http.Handler) http.Handler { // Resolve scene var scene *models.Scene - _ = txn.WithReadTxn(r.Context(), rs.TxnManager, func(ctx context.Context) error { + _ = rs.withReadTxn(r.Context(), func(ctx context.Context) error { qb := rs.SceneFinder scene, _ = qb.Find(ctx, sceneID) diff --git a/internal/heresphere/tags_read.go b/internal/heresphere/tags_read.go index 82a820e6cc7..7b97606f73c 100644 --- a/internal/heresphere/tags_read.go +++ b/internal/heresphere/tags_read.go @@ -7,7 +7,6 @@ import ( "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/txn" ) /* @@ -16,7 +15,7 @@ import ( func (rs routes) getVideoTags(ctx context.Context, scene *models.Scene) []HeresphereVideoTag { processedTags := []HeresphereVideoTag{} - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + if err := rs.withReadTxn(ctx, func(ctx context.Context) error { err := scene.LoadRelationships(ctx, rs.SceneFinder) processedTags = append(processedTags, rs.generateMarkerTags(ctx, scene)...) diff --git a/internal/heresphere/tags_write.go b/internal/heresphere/tags_write.go index 6c13b707ce3..59361c91b97 100644 --- a/internal/heresphere/tags_write.go +++ b/internal/heresphere/tags_write.go @@ -10,7 +10,6 @@ import ( "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/txn" ) /* @@ -85,7 +84,7 @@ func (rs routes) handleAddTag(ctx context.Context, tag HeresphereVideoTag, tagID after := strings.TrimPrefix(tag.Name, "Tag:") var err error var tagMod *models.Tag - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + if err := rs.withReadTxn(ctx, func(ctx context.Context) error { // Search for tag tagMod, err = rs.TagFinder.FindByName(ctx, after, true) return err @@ -108,7 +107,7 @@ func (rs routes) handleAddPerformer(ctx context.Context, tag HeresphereVideoTag, after := strings.TrimPrefix(tag.Name, "Performer:") var err error var tagMod *models.Performer - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + if err := rs.withReadTxn(ctx, func(ctx context.Context) error { var tagMods []*models.Performer // Search for performer @@ -136,7 +135,7 @@ func (rs routes) handleAddMarker(ctx context.Context, tag HeresphereVideoTag, sc after := strings.TrimPrefix(tag.Name, "Marker:") var tagId *string - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + if err := rs.withReadTxn(ctx, func(ctx context.Context) error { var err error var markerResult []*models.MarkerStringsResultType searchType := "count" @@ -178,7 +177,7 @@ func (rs routes) handleAddMarker(ctx context.Context, tag HeresphereVideoTag, sc UpdatedAt: currentTime, } - if err := txn.WithTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + if err := rs.withTxn(ctx, func(ctx context.Context) error { return rs.SceneMarkerFinder.Create(ctx, &newMarker) }); err != nil { logger.Errorf("Heresphere handleTags SceneMarker.Create error: %s\n", err.Error()) @@ -197,7 +196,7 @@ func (rs routes) handleAddStudio(ctx context.Context, tag HeresphereVideoTag, sc var err error var tagMod *models.Studio - if err := txn.WithReadTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + if err := rs.withReadTxn(ctx, func(ctx context.Context) error { // Search for performer tagMod, err = rs.StudioFinder.FindByName(ctx, after, true) return err diff --git a/internal/heresphere/update.go b/internal/heresphere/update.go index dacec838469..3803c47fa30 100644 --- a/internal/heresphere/update.go +++ b/internal/heresphere/update.go @@ -6,7 +6,6 @@ import ( "github.com/stashapp/stash/pkg/file" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/txn" ) /* @@ -35,7 +34,7 @@ func (rs routes) updatePlayCount(ctx context.Context, scn *models.Scene, event H ret.Partial.PlayCount.Set = true ret.Partial.PlayCount.Value = scn.PlayCount + 1 - err := txn.WithTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + err := rs.withTxn(ctx, func(ctx context.Context) error { _, err := ret.Update(ctx, rs.SceneFinder) return err }) @@ -50,7 +49,7 @@ func (rs routes) updatePlayCount(ctx context.Context, scn *models.Scene, event H * Deletes the scene's primary file */ func (rs routes) handleDeletePrimaryFile(ctx context.Context, scn *models.Scene, fileDeleter *file.Deleter) (bool, error) { - err := txn.WithTxn(ctx, rs.TxnManager, func(ctx context.Context) error { + err := rs.withTxn(ctx, func(ctx context.Context) error { if err := scn.LoadPrimaryFile(ctx, rs.FileFinder); err != nil { return err } diff --git a/pkg/models/rating.go b/pkg/models/rating.go index cf35036119b..6529d0bc018 100644 --- a/pkg/models/rating.go +++ b/pkg/models/rating.go @@ -64,7 +64,7 @@ func Rating100To5(rating100 int) int { } func Rating100To5F(rating100 int) float64 { val := math.Round((float64(rating100) / 20.0)) - return float64(math.Max(minRating5, math.Min(maxRating5, val))) + return math.Max(minRating5, math.Min(maxRating5, val)) } // Rating5To100 converts a 1-5 rating to a 1-100 rating diff --git a/pkg/models/routes.go b/pkg/models/routes.go deleted file mode 100644 index 88fe2e54a74..00000000000 --- a/pkg/models/routes.go +++ /dev/null @@ -1,15 +0,0 @@ -package models - -import ( - "net/http" - - "github.com/stashapp/stash/pkg/txn" -) - -type TxnRoutes struct { - TxnManager txn.Manager -} - -func (rs TxnRoutes) WithReadTxn(r *http.Request, fn txn.TxnFunc) error { - return txn.WithReadTxn(r.Context(), rs.TxnManager, fn) -} From 448a51e87335abb61686e1a31f6726a013f42ba2 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:03:24 +0200 Subject: [PATCH 138/144] Bugfix from prev --- internal/heresphere/interfaces.go | 6 +++--- internal/heresphere/routes.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/heresphere/interfaces.go b/internal/heresphere/interfaces.go index e14b64cde2d..82398f0bc02 100644 --- a/internal/heresphere/interfaces.go +++ b/internal/heresphere/interfaces.go @@ -8,15 +8,15 @@ import ( ) // Repository provides access to storage methods for files and folders. -type Repository struct { +type repository struct { TxnManager models.TxnManager } -func (r *Repository) withTxn(ctx context.Context, fn txn.TxnFunc) error { +func (r *repository) withTxn(ctx context.Context, fn txn.TxnFunc) error { return txn.WithTxn(ctx, r.TxnManager, fn) } -func (r *Repository) withReadTxn(ctx context.Context, fn txn.TxnFunc) error { +func (r *repository) withReadTxn(ctx context.Context, fn txn.TxnFunc) error { return txn.WithReadTxn(ctx, r.TxnManager, fn) } diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index a792b55c53d..c98c5a9ef69 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -39,7 +39,7 @@ const ( ) type routes struct { - Repository + repository SceneFinder sceneFinder SceneMarkerFinder sceneMarkerFinder FileFinder fileFinder @@ -53,7 +53,7 @@ type routes struct { func GetRoutes(repo models.Repository) chi.Router { return routes{ - Repository: Repository{TxnManager: repo.TxnManager}, + repository: repository{TxnManager: repo.TxnManager}, SceneFinder: repo.Scene, SceneMarkerFinder: repo.SceneMarker, FileFinder: repo.File, From 194f8cbfeb6e25b6f2a13bff3e610d8a1bde4097 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:21:54 +0200 Subject: [PATCH 139/144] prettier --- .../src/components/Settings/SettingsServicesPanel.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index a2709e5cb0b..22d2471f2ea 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -37,7 +37,14 @@ export const SettingsServicesPanel: React.FC = () => { const intl = useIntl(); const Toast = useToast(); - const { dlna, hsp, loading: configLoading, error, saveDLNA, saveHSP } = useSettings(); + const { + dlna, + hsp, + loading: configLoading, + error, + saveDLNA, + saveHSP, + } = useSettings(); // undefined to hide dialog, true for enable, false for disable const [enableDisable, setEnableDisable] = useState(); From 5941392bc5d16196bf0edb2e9174b2d91995edcd Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 31 Oct 2023 21:47:07 +0100 Subject: [PATCH 140/144] Proper scene deletion --- internal/heresphere/interfaces.go | 5 ++++ internal/heresphere/routes.go | 13 +++++---- internal/heresphere/update.go | 45 +++++++++++++++++++++++++------ ui/v2.5/src/locales/en-GB.json | 8 +++--- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/internal/heresphere/interfaces.go b/internal/heresphere/interfaces.go index 82398f0bc02..7b1e5bfa88c 100644 --- a/internal/heresphere/interfaces.go +++ b/internal/heresphere/interfaces.go @@ -4,6 +4,7 @@ import ( "context" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/plugin" "github.com/stashapp/stash/pkg/txn" ) @@ -62,3 +63,7 @@ type movieFinder interface { type studioFinder interface { models.StudioFinder } + +type hookExecutor interface { + ExecutePostHooks(ctx context.Context, id int, hookType plugin.HookTriggerEnum, input interface{}, inputFields []string) +} diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index c98c5a9ef69..c5a7d7caa17 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -16,7 +16,6 @@ import ( "github.com/stashapp/stash/internal/api/urlbuilders" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" - "github.com/stashapp/stash/pkg/file" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" @@ -41,6 +40,7 @@ const ( type routes struct { repository SceneFinder sceneFinder + SceneService manager.SceneService SceneMarkerFinder sceneMarkerFinder FileFinder fileFinder TagFinder tagFinder @@ -49,12 +49,14 @@ type routes struct { GalleryFinder galleryFinder MovieFinder movieFinder StudioFinder studioFinder + HookExecutor hookExecutor } func GetRoutes(repo models.Repository) chi.Router { return routes{ repository: repository{TxnManager: repo.TxnManager}, SceneFinder: repo.Scene, + SceneService: manager.GetInstance().SceneService, SceneMarkerFinder: repo.SceneMarker, FileFinder: repo.File, TagFinder: repo.Tag, @@ -63,6 +65,7 @@ func GetRoutes(repo models.Repository) chi.Router { GalleryFinder: repo.Gallery, MovieFinder: repo.Movie, StudioFinder: repo.Studio, + HookExecutor: manager.GetInstance().PluginCache, }.Routes() } @@ -161,7 +164,6 @@ func (rs routes) heresphereVideoEvent(w http.ResponseWriter, r *http.Request) { func (rs routes) heresphereVideoDataUpdate(w http.ResponseWriter, r *http.Request) error { scn := r.Context().Value(sceneKey).(*models.Scene) user := r.Context().Value(authKey).(HeresphereAuthReq) - fileDeleter := file.NewDeleter() c := config.GetInstance() shouldUpdate := false @@ -174,18 +176,16 @@ func (rs routes) heresphereVideoDataUpdate(w http.ResponseWriter, r *http.Reques var err error if user.Rating != nil && c.GetHSPWriteRatings() { if b, err = rs.updateRating(user, ret); err != nil { - fileDeleter.Rollback() return err } shouldUpdate = b || shouldUpdate } if user.DeleteFile != nil && *user.DeleteFile && c.GetHSPWriteDeletes() { - if b, err = rs.handleDeletePrimaryFile(r.Context(), scn, fileDeleter); err != nil { - fileDeleter.Rollback() + if b, err = rs.handleDeleteScene(r.Context(), scn); err != nil { return err } - shouldUpdate = b || shouldUpdate + return fmt.Errorf("file was deleted") } if user.IsFavorite != nil && c.GetHSPWriteFavorites() { @@ -210,7 +210,6 @@ func (rs routes) heresphereVideoDataUpdate(w http.ResponseWriter, r *http.Reques return err } - fileDeleter.Commit() return nil } return nil diff --git a/internal/heresphere/update.go b/internal/heresphere/update.go index 3803c47fa30..d75086afa56 100644 --- a/internal/heresphere/update.go +++ b/internal/heresphere/update.go @@ -2,9 +2,12 @@ package heresphere import ( "context" + "strconv" + "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/pkg/file" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/plugin" "github.com/stashapp/stash/pkg/scene" ) @@ -48,19 +51,45 @@ func (rs routes) updatePlayCount(ctx context.Context, scn *models.Scene, event H /* * Deletes the scene's primary file */ -func (rs routes) handleDeletePrimaryFile(ctx context.Context, scn *models.Scene, fileDeleter *file.Deleter) (bool, error) { +func (rs routes) handleDeleteScene(ctx context.Context, scn *models.Scene) (bool, error) { err := rs.withTxn(ctx, func(ctx context.Context) error { - if err := scn.LoadPrimaryFile(ctx, rs.FileFinder); err != nil { - return err + // Construct scene deletion + deleteFile := true + deleteGenerated := true + input := models.ScenesDestroyInput{ + Ids: []string{strconv.Itoa(scn.ID)}, + DeleteFile: &deleteFile, + DeleteGenerated: &deleteGenerated, } - ff := scn.Files.Primary() - if ff != nil { - if err := file.Destroy(ctx, rs.FileFinder, ff, fileDeleter, true); err != nil { - return err - } + // Construct file deleter + fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm() + fileDeleter := &scene.FileDeleter{ + Deleter: file.NewDeleter(), + FileNamingAlgo: fileNamingAlgo, + Paths: manager.GetInstance().Paths, } + // Kill running streams + manager.KillRunningStreams(scn, fileNamingAlgo) + + // Destroy scene + if err := rs.SceneService.Destroy(ctx, scn, fileDeleter, deleteGenerated, deleteFile); err != nil { + fileDeleter.Rollback() + return err + } + + // Commit deletion + fileDeleter.Commit() + + // Plugin callback + rs.HookExecutor.ExecutePostHooks(ctx, scn.ID, plugin.SceneDestroyPost, plugin.ScenesDestroyInput{ + ScenesDestroyInput: input, + Checksum: scn.Checksum, + OSHash: scn.OSHash, + Path: scn.Path, + }, nil) + return nil }) return err == nil, err diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 9d2ed17a9a2..144e7ea9f5b 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -261,11 +261,11 @@ "write_favorites": "Write Favorites", "write_favorites_desc": "Whether to enable HSP to write the favorite tag to videos", "write_ratings": "Write Ratings", - "write_ratings_desc": "Wether to enable HSP to write ratings", + "write_ratings_desc": "Whether to enable HSP to write ratings", "write_tags": "Write Tags", - "write_tags_desc": "Wether to enable HSP to write tags", - "write_deletes": "Do deletes", - "write_deletes_desc": "Wether to allow file deletion via HSP API" + "write_tags_desc": "Whether to enable HSP to write tags", + "write_deletes": "Allow deletes", + "write_deletes_desc": "Whether to allow scene deletion via HSP API" }, "general": { "auth": { From 1c5a88e7d3644f13720206f463f98af20fea8070 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 31 Oct 2023 21:56:30 +0100 Subject: [PATCH 141/144] Linter fix --- internal/heresphere/routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index c5a7d7caa17..06e29c171f0 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -182,7 +182,7 @@ func (rs routes) heresphereVideoDataUpdate(w http.ResponseWriter, r *http.Reques } if user.DeleteFile != nil && *user.DeleteFile && c.GetHSPWriteDeletes() { - if b, err = rs.handleDeleteScene(r.Context(), scn); err != nil { + if _, err = rs.handleDeleteScene(r.Context(), scn); err != nil { return err } return fmt.Errorf("file was deleted") From fbb2d81a6d00dd17963cf1a2b29f4c01c48fce6d Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 23 Nov 2023 01:42:45 +0100 Subject: [PATCH 142/144] Fix relationships.go and chi in hsp routes.go --- internal/heresphere/routes.go | 2 +- pkg/models/relationships.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/heresphere/routes.go b/internal/heresphere/routes.go index 06e29c171f0..170c9655fae 100644 --- a/internal/heresphere/routes.go +++ b/internal/heresphere/routes.go @@ -12,7 +12,7 @@ import ( "strconv" "strings" - "github.com/go-chi/chi" + "github.com/go-chi/chi/v5" "github.com/stashapp/stash/internal/api/urlbuilders" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager/config" diff --git a/pkg/models/relationships.go b/pkg/models/relationships.go index 038ff08a2ca..379c18ba568 100644 --- a/pkg/models/relationships.go +++ b/pkg/models/relationships.go @@ -3,7 +3,7 @@ package models import ( "context" - "github.com/stashapp/stash/pkg/sliceutil/intslice" + "github.com/stashapp/stash/pkg/sliceutil" ) type SceneIDLoader interface { @@ -90,14 +90,14 @@ func (r RelatedIDs) List() []int { func (r *RelatedIDs) Add(ids ...int) { r.mustLoaded() - r.list = intslice.IntAppendUniques(r.list, ids) + r.list = sliceutil.AppendUniques(r.list, ids) } // Remove removes the provided ids to the list. Panics if the relationship has not been loaded. func (r *RelatedIDs) Remove(ids ...int) { r.mustLoaded() - r.list = intslice.IntExclude(r.list, ids) + r.list = sliceutil.Exclude(r.list, ids) } func (r *RelatedIDs) load(fn func() ([]int, error)) error { From f6fa9751f5fad618c42b311d28ef59dde28caf65 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Thu, 23 Nov 2023 01:52:29 +0100 Subject: [PATCH 143/144] Some ui context.tsx change --- ui/v2.5/src/components/Settings/context.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/v2.5/src/components/Settings/context.tsx b/ui/v2.5/src/components/Settings/context.tsx index cdb792b4fba..c2b26a243c3 100644 --- a/ui/v2.5/src/components/Settings/context.tsx +++ b/ui/v2.5/src/components/Settings/context.tsx @@ -401,7 +401,7 @@ export const SettingsContext: React.FC = ({ children }) => { setPendingHSP(undefined); onSuccess(); } catch (e) { - setSaveError(e); + onError(e); } }, 500); From b2dbb29ed262d05c51eebb22d205708948834c44 Mon Sep 17 00:00:00 2001 From: Nodude <75137537+NodudeWasTaken@users.noreply.github.com> Date: Fri, 1 Dec 2023 00:21:45 +0100 Subject: [PATCH 144/144] Also use default filter --- internal/heresphere/filters.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/internal/heresphere/filters.go b/internal/heresphere/filters.go index b6332c7c3e2..d41c0d30a1b 100644 --- a/internal/heresphere/filters.go +++ b/internal/heresphere/filters.go @@ -47,6 +47,23 @@ func (rs routes) getAllFilters(ctx context.Context) (scenesMap map[string][]int, return } + dfilter, err := func() (*models.SavedFilter, error) { + var filter *models.SavedFilter + err = rs.withReadTxn(ctx, func(ctx context.Context) error { + filter, err = rs.FilterFinder.FindDefault(ctx, models.FilterModeScenes) + return err + }) + return filter, err + }() + + if err != nil { + err = fmt.Errorf("heresphere FilterTest SavedFilter.FindDefault error: %s", err.Error()) + return + } + + dfilter.Name = "Default" + savedfilters = append(savedfilters, dfilter) + for _, savedfilter := range savedfilters { filter := savedfilter.FindFilter sceneFilter, err := parseObjectFilter(savedfilter)