Compare commits
	
		
			6 Commits
		
	
	
		
			settings_r
			...
			dependency
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6e0928a97a | |||
| 05ea72a8ca | |||
| 7476e2397d | |||
| 929e0c337d | |||
| f7a0d8fa07 | |||
| 9faf457c89 | 
@@ -146,8 +146,8 @@ Test_Server_MANUAL:
 | 
			
		||||
    - echo "$SSH_PRIVATE_KEY_2" | ssh-add -
 | 
			
		||||
    - mkdir -p ~/.ssh
 | 
			
		||||
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
 | 
			
		||||
    - scp deb/OpenMediaCenter-*.deb root@192.168.0.209:/tmp/
 | 
			
		||||
    - ssh root@192.168.0.209 "DEBIAN_FRONTEND=noninteractive apt-get --reinstall  -y -qq install /tmp/OpenMediaCenter-*.deb && rm /tmp/OpenMediaCenter-*.deb"
 | 
			
		||||
    - scp deb/OpenMediaCenter-*.deb root@192.168.0.44:/tmp/
 | 
			
		||||
    - ssh root@192.168.0.44 "DEBIAN_FRONTEND=noninteractive apt-get --reinstall  -y -qq install /tmp/OpenMediaCenter-*.deb && rm /tmp/OpenMediaCenter-*.deb"
 | 
			
		||||
  allow_failure: true
 | 
			
		||||
 | 
			
		||||
Test_Server_2_CD:
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,6 @@ func getSettingsFromDB() {
 | 
			
		||||
			TVShowPath        string
 | 
			
		||||
			TVShowEnabled     bool
 | 
			
		||||
			FullDeleteEnabled bool
 | 
			
		||||
			RandomNR          uint32
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		regexMatchUrl := regexp.MustCompile("^http(|s)://([0-9]){1,3}\\.([0-9]){1,3}\\.([0-9]){1,3}\\.([0-9]){1,3}:[0-9]{1,5}")
 | 
			
		||||
@@ -91,7 +90,6 @@ func getSettingsFromDB() {
 | 
			
		||||
			TVShowPath:        serverTVShowPath,
 | 
			
		||||
			TVShowEnabled:     !config.GetConfig().Features.DisableTVSupport,
 | 
			
		||||
			FullDeleteEnabled: config.GetConfig().Features.FullyDeletableVideos,
 | 
			
		||||
			RandomNR:          sett.RandomNR,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		context.Json(res)
 | 
			
		||||
@@ -129,13 +127,12 @@ func saveSettingsToDB() {
 | 
			
		||||
                        password=?,
 | 
			
		||||
                        mediacenter_name=?,
 | 
			
		||||
                        TMDB_grabbing=?, 
 | 
			
		||||
                        DarkMode=?,
 | 
			
		||||
						random_nr=?
 | 
			
		||||
                        DarkMode=?
 | 
			
		||||
                    WHERE 1`
 | 
			
		||||
		// todo avoid conversion
 | 
			
		||||
		context.Text(string(database.SuccessQuery(query,
 | 
			
		||||
			args.VideoPath, args.EpisodePath, args.Password,
 | 
			
		||||
			args.MediacenterName, args.TMDBGrabbing, args.DarkMode, args.RandomNR)))
 | 
			
		||||
			args.MediacenterName, args.TMDBGrabbing, args.DarkMode)))
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ func getFromDB() {
 | 
			
		||||
	 * @apiSuccess {string} TagName name of the Tag
 | 
			
		||||
	 */
 | 
			
		||||
	api.AddHandler("getAllTags", api.TagNode, api.PermUser, func(context api.Context) {
 | 
			
		||||
		query := "SELECT tag_id,tag_name from tags"
 | 
			
		||||
		query := "SELECT tag_id,tag_name from tags ORDER BY tag_name"
 | 
			
		||||
		context.Json(readTagsFromResultset(database.Query(query)))
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,6 @@ type SettingsType struct {
 | 
			
		||||
	PasswordEnabled bool
 | 
			
		||||
	TMDBGrabbing    bool
 | 
			
		||||
	DarkMode        bool
 | 
			
		||||
	RandomNR        uint32
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SettingsSizeType struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -126,7 +126,7 @@ func GetSettings() (result types.SettingsType, PathPrefix string, sizes types.Se
 | 
			
		||||
                           SELECT COUNT(*)
 | 
			
		||||
                           FROM video_tags
 | 
			
		||||
                       ) AS tagsadded,
 | 
			
		||||
                       video_path, episode_path, password, mediacenter_name, TMDB_grabbing, DarkMode, random_nr
 | 
			
		||||
                       video_path, episode_path, password, mediacenter_name, TMDB_grabbing, DarkMode
 | 
			
		||||
                FROM settings
 | 
			
		||||
                LIMIT 1`, DBName)
 | 
			
		||||
 | 
			
		||||
@@ -134,7 +134,7 @@ func GetSettings() (result types.SettingsType, PathPrefix string, sizes types.Se
 | 
			
		||||
	var TMDBGrabbing int
 | 
			
		||||
 | 
			
		||||
	err := QueryRow(query).Scan(&sizes.VideoNr, &sizes.DBSize, &sizes.DifferentTags, &sizes.TagsAdded,
 | 
			
		||||
		&result.VideoPath, &result.EpisodePath, &result.Password, &result.MediacenterName, &TMDBGrabbing, &DarkMode, &result.RandomNR)
 | 
			
		||||
		&result.VideoPath, &result.EpisodePath, &result.Password, &result.MediacenterName, &TMDBGrabbing, &DarkMode)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err.Error())
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
-- +goose Up
 | 
			
		||||
-- +goose StatementBegin
 | 
			
		||||
alter table settings
 | 
			
		||||
    add random_nr int default  3 null;
 | 
			
		||||
-- +goose StatementEnd
 | 
			
		||||
 | 
			
		||||
-- +goose Down
 | 
			
		||||
-- +goose StatementBegin
 | 
			
		||||
alter table settings
 | 
			
		||||
    drop random_nr;
 | 
			
		||||
-- +goose StatementEnd
 | 
			
		||||
@@ -20,16 +20,15 @@ type SettingsType struct {
 | 
			
		||||
	MediacenterName string
 | 
			
		||||
	VideoPath       string
 | 
			
		||||
	TVShowPath      string
 | 
			
		||||
	RandomNR        uint32
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoadSettings() *SettingsType {
 | 
			
		||||
	query := "SELECT DarkMode, password, mediacenter_name, video_path, episode_path, random_nr from settings"
 | 
			
		||||
	query := "SELECT DarkMode, password, mediacenter_name, video_path, episode_path from settings"
 | 
			
		||||
 | 
			
		||||
	result := SettingsType{}
 | 
			
		||||
	var darkmode uint8
 | 
			
		||||
 | 
			
		||||
	err := database.QueryRow(query).Scan(&darkmode, &result.Pasword, &result.MediacenterName, &result.VideoPath, &result.TVShowPath, &result.RandomNR)
 | 
			
		||||
	err := database.QueryRow(query).Scan(&darkmode, &result.Pasword, &result.MediacenterName, &result.VideoPath, &result.TVShowPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("error while parsing db data: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ go 1.16
 | 
			
		||||
require (
 | 
			
		||||
	github.com/3d0c/gmf v0.0.0-20210925211039-e278e6e53b16
 | 
			
		||||
	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 | 
			
		||||
	github.com/fsnotify/fsnotify v1.5.4
 | 
			
		||||
	github.com/go-sql-driver/mysql v1.6.0
 | 
			
		||||
	github.com/pelletier/go-toml/v2 v2.0.0-beta.3
 | 
			
		||||
	github.com/pressly/goose/v3 v3.1.0
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
 | 
			
		||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 | 
			
		||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 | 
			
		||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
 | 
			
		||||
@@ -82,8 +84,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								apiGo/housekeeping/HouseKeeping.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								apiGo/housekeeping/HouseKeeping.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
package housekeeping
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
func RunHouseKeepingTasks() {
 | 
			
		||||
	fmt.Println("Runnint houskeeping tasks!")
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Deduplicating Tags")
 | 
			
		||||
	deduplicateTags()
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Deduplicating Tags assigned to videos")
 | 
			
		||||
	deduplicateVideoTags()
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Fix missing video metadata like ratio")
 | 
			
		||||
	fixMissingMetadata()
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Finished housekeeping")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								apiGo/housekeeping/MissingVideoMetaData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								apiGo/housekeeping/MissingVideoMetaData.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
package housekeeping
 | 
			
		||||
 | 
			
		||||
func fixMissingMetadata() {
 | 
			
		||||
	// todo
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								apiGo/housekeeping/TagDeduplication.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								apiGo/housekeeping/TagDeduplication.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
package housekeeping
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"openmediacenter/apiGo/database"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func deduplicateTags() {
 | 
			
		||||
	// find all duplicate tags
 | 
			
		||||
 | 
			
		||||
	// gives first occurence of duplicate
 | 
			
		||||
	query := `
 | 
			
		||||
SELECT
 | 
			
		||||
    tag_name
 | 
			
		||||
FROM
 | 
			
		||||
    tags
 | 
			
		||||
GROUP BY tag_name
 | 
			
		||||
HAVING COUNT(tag_name) > 1`
 | 
			
		||||
	rows := database.Query(query)
 | 
			
		||||
	duplicates := []string{}
 | 
			
		||||
	if rows != nil {
 | 
			
		||||
		for rows.Next() {
 | 
			
		||||
			var id string
 | 
			
		||||
			err := rows.Scan(&id)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				panic(err.Error()) // proper Error handling instead of panic in your app
 | 
			
		||||
			}
 | 
			
		||||
			duplicates = append(duplicates, id)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// nothing to do
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Print("deleting duplicate tag ids: ")
 | 
			
		||||
	fmt.Println(duplicates)
 | 
			
		||||
 | 
			
		||||
	for _, el := range duplicates {
 | 
			
		||||
		query := fmt.Sprintf("SELECT tag_id FROM tags WHERE tag_name='%s'", el)
 | 
			
		||||
		rows := database.Query(query)
 | 
			
		||||
		ids := []uint32{}
 | 
			
		||||
		for rows.Next() {
 | 
			
		||||
			var id uint32
 | 
			
		||||
			err := rows.Scan(&id)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				panic(err.Error()) // proper Error handling instead of panic in your app
 | 
			
		||||
			}
 | 
			
		||||
			ids = append(ids, id)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// id to copy other data to
 | 
			
		||||
		mainid := ids[0]
 | 
			
		||||
 | 
			
		||||
		// ids to copy from
 | 
			
		||||
		copyids := ids[1:]
 | 
			
		||||
 | 
			
		||||
		fmt.Printf("Migrating %s\n", el)
 | 
			
		||||
		migrateTags(mainid, copyids)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func migrateTags(destid uint32, sourcids []uint32) {
 | 
			
		||||
	querytempl := `
 | 
			
		||||
UPDATE video_tags 
 | 
			
		||||
SET 
 | 
			
		||||
    tag_id = %d
 | 
			
		||||
WHERE
 | 
			
		||||
    tag_id = %d`
 | 
			
		||||
 | 
			
		||||
	for _, id := range sourcids {
 | 
			
		||||
		err := database.Edit(fmt.Sprintf(querytempl, destid, id))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Printf("failed to set id from %d to %d\n", id, destid)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("Merged %d into %d\n", id, destid)
 | 
			
		||||
 | 
			
		||||
		// now lets delete this tag
 | 
			
		||||
		query := fmt.Sprintf(`DELETE FROM tags WHERE tag_id=%d`, id)
 | 
			
		||||
		err = database.Edit(query)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Printf("failed to delete Tag %d", id)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								apiGo/housekeeping/VideoTagDeduplication.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								apiGo/housekeeping/VideoTagDeduplication.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
package housekeeping
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"openmediacenter/apiGo/database"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func deduplicateVideoTags() {
 | 
			
		||||
	// gives first occurence of duplicate
 | 
			
		||||
	query := `
 | 
			
		||||
SELECT
 | 
			
		||||
    tag_id, video_id, count(tag_id)
 | 
			
		||||
FROM
 | 
			
		||||
    video_tags
 | 
			
		||||
GROUP BY tag_id, video_id
 | 
			
		||||
HAVING COUNT(tag_id) > 1`
 | 
			
		||||
	rows := database.Query(query)
 | 
			
		||||
	if rows != nil {
 | 
			
		||||
		for rows.Next() {
 | 
			
		||||
			var tagid uint32
 | 
			
		||||
			var vidid uint32
 | 
			
		||||
			var nr uint32
 | 
			
		||||
			err := rows.Scan(&tagid, &vidid, &nr)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				panic(err.Error()) // proper Error handling instead of panic in your app
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// now lets delete this tag
 | 
			
		||||
			query := fmt.Sprintf(`DELETE FROM video_tags WHERE tag_id=%d AND video_id=%d LIMIT %d`, tagid, vidid, nr-1)
 | 
			
		||||
			err = database.Edit(query)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				fmt.Printf("failed to delete Tag %d + vid %d", tagid, vidid)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"openmediacenter/apiGo/api"
 | 
			
		||||
	api2 "openmediacenter/apiGo/api/api"
 | 
			
		||||
	"openmediacenter/apiGo/config"
 | 
			
		||||
	"openmediacenter/apiGo/database"
 | 
			
		||||
	"openmediacenter/apiGo/housekeeping"
 | 
			
		||||
	"openmediacenter/apiGo/static"
 | 
			
		||||
	"openmediacenter/apiGo/videoparser"
 | 
			
		||||
	"os"
 | 
			
		||||
@@ -17,6 +19,8 @@ func main() {
 | 
			
		||||
	const port uint16 = 8081
 | 
			
		||||
	errc := make(chan error, 1)
 | 
			
		||||
 | 
			
		||||
	housekPTr := flag.Bool("HouseKeeping", false, "Run housekeeping tasks")
 | 
			
		||||
 | 
			
		||||
	config.Init()
 | 
			
		||||
 | 
			
		||||
	// todo some verbosity logger or sth
 | 
			
		||||
@@ -29,9 +33,16 @@ func main() {
 | 
			
		||||
	}
 | 
			
		||||
	defer database.Close()
 | 
			
		||||
 | 
			
		||||
	// check if we should run the housekeeping tasks
 | 
			
		||||
	if *housekPTr {
 | 
			
		||||
		housekeeping.RunHouseKeepingTasks()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.AddHandlers()
 | 
			
		||||
 | 
			
		||||
	videoparser.SetupSettingsWebsocket()
 | 
			
		||||
	videoparser.InitFileWatcher()
 | 
			
		||||
 | 
			
		||||
	// add the static files
 | 
			
		||||
	static.ServeStaticFiles()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								apiGo/videoparser/FileWatcher.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								apiGo/videoparser/FileWatcher.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
package videoparser
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/fsnotify/fsnotify"
 | 
			
		||||
	"log"
 | 
			
		||||
	"openmediacenter/apiGo/config"
 | 
			
		||||
	"openmediacenter/apiGo/database"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InitFileWatcher() {
 | 
			
		||||
	watcher, err := fsnotify.NewWatcher()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal("NewWatcher failed: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mSettings, _, _ := database.GetSettings()
 | 
			
		||||
	vidFolder := config.GetConfig().General.ReindexPrefix + mSettings.VideoPath
 | 
			
		||||
	epsfolder := config.GetConfig().General.ReindexPrefix + mSettings.EpisodePath
 | 
			
		||||
 | 
			
		||||
	defer watcher.Close()
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			select {
 | 
			
		||||
			case event, ok := <-watcher.Events:
 | 
			
		||||
				if !ok {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				// start new reindex
 | 
			
		||||
				// (may be optimized by checking here if added file is video
 | 
			
		||||
				// and start reindex just for one file)
 | 
			
		||||
				if strings.Contains(event.Name, vidFolder) {
 | 
			
		||||
					StartReindex()
 | 
			
		||||
				} else if strings.Contains(event.Name, epsfolder) {
 | 
			
		||||
					StartTVShowReindex()
 | 
			
		||||
				} else {
 | 
			
		||||
					log.Printf("Event in wrong folder: %s %s\n", event.Name, event.Op)
 | 
			
		||||
				}
 | 
			
		||||
			case err, ok := <-watcher.Errors:
 | 
			
		||||
				if !ok {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				log.Println("error:", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	err = watcher.Add(vidFolder)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println("Adding of file watcher failed: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = watcher.Add(epsfolder)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println("Adding of file watcher failed: ", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,6 @@ import (
 | 
			
		||||
	"openmediacenter/apiGo/config"
 | 
			
		||||
	"openmediacenter/apiGo/database"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -33,22 +32,19 @@ func StartReindex() bool {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var files []string
 | 
			
		||||
	err := filepath.Walk(vidFolder, func(path string, info os.FileInfo, err error) error {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println(err.Error())
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !info.IsDir() && strings.HasSuffix(info.Name(), ".mp4") {
 | 
			
		||||
			files = append(files, info.Name())
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	filelist, err := ioutil.ReadDir(vidFolder)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err.Error())
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var files []string
 | 
			
		||||
	for _, file := range filelist {
 | 
			
		||||
		if !file.IsDir() && strings.HasSuffix(file.Name(), ".mp4") {
 | 
			
		||||
			files = append(files, file.Name())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// start reindex process
 | 
			
		||||
	AppendMessage("Starting Reindexing!")
 | 
			
		||||
	InitDeps(&mSettings)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								package.json
									
									
									
									
									
								
							@@ -8,17 +8,17 @@
 | 
			
		||||
    "url": "https://heili.eu"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@fortawesome/fontawesome-svg-core": "^1.2.32",
 | 
			
		||||
    "@fortawesome/free-regular-svg-icons": "^5.15.1",
 | 
			
		||||
    "@fortawesome/free-solid-svg-icons": "^5.15.1",
 | 
			
		||||
    "@fortawesome/react-fontawesome": "^0.1.13",
 | 
			
		||||
    "@fortawesome/fontawesome-svg-core": "^6.2.0",
 | 
			
		||||
    "@fortawesome/free-regular-svg-icons": "^6.2.0",
 | 
			
		||||
    "@fortawesome/free-solid-svg-icons": "^6.2.0",
 | 
			
		||||
    "@fortawesome/react-fontawesome": "^0.2.0",
 | 
			
		||||
    "bootstrap": "^5.0.2",
 | 
			
		||||
    "plyr-react": "^3.0.7",
 | 
			
		||||
    "react": "^17.0.1",
 | 
			
		||||
    "react-bootstrap": "^1.4.0",
 | 
			
		||||
    "react-dom": "^17.0.1",
 | 
			
		||||
    "react-router": "^5.2.0",
 | 
			
		||||
    "react-router-dom": "^5.2.0",
 | 
			
		||||
    "plyr-react": "^5.1.0",
 | 
			
		||||
    "react": "^18.2.0",
 | 
			
		||||
    "react-bootstrap": "^2.5.0",
 | 
			
		||||
    "react-dom": "^18.2.0",
 | 
			
		||||
    "react-router": "^6.4.0",
 | 
			
		||||
    "react-router-dom": "^6.4.0",
 | 
			
		||||
    "typescript": "^4.3.5"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
@@ -54,31 +54,31 @@
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@testing-library/jest-dom": "^5.14.1",
 | 
			
		||||
    "@testing-library/react": "^12.0.0",
 | 
			
		||||
    "@testing-library/user-event": "^13.2.1",
 | 
			
		||||
    "@types/jest": "^26.0.24",
 | 
			
		||||
    "@types/node": "^16.4.7",
 | 
			
		||||
    "@types/react": "^17.0.15",
 | 
			
		||||
    "@types/react-dom": "^17.0.9",
 | 
			
		||||
    "@types/react-router": "5.1.16",
 | 
			
		||||
    "@testing-library/react": "^13.4.0",
 | 
			
		||||
    "@testing-library/user-event": "^14.4.3",
 | 
			
		||||
    "@types/jest": "^29.0.3",
 | 
			
		||||
    "@types/node": "^18.7.18",
 | 
			
		||||
    "@types/react": "^18.0.20",
 | 
			
		||||
    "@types/react-dom": "^18.0.6",
 | 
			
		||||
    "@types/react-router": "5.1.19",
 | 
			
		||||
    "@types/react-router-dom": "^5.1.8",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^4.28.5",
 | 
			
		||||
    "@typescript-eslint/parser": "^4.28.5",
 | 
			
		||||
    "apidoc": "^0.28.1",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.38.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.38.0",
 | 
			
		||||
    "apidoc": "^0.53.0",
 | 
			
		||||
    "enzyme": "^3.11.0",
 | 
			
		||||
    "enzyme-adapter-react-16": "^1.15.5",
 | 
			
		||||
    "eslint": "^7.31.0",
 | 
			
		||||
    "eslint": "^8.23.1",
 | 
			
		||||
    "eslint-config-prettier": "^8.1.0",
 | 
			
		||||
    "eslint-formatter-gitlab": "^2.2.0",
 | 
			
		||||
    "eslint-formatter-gitlab": "^3.0.0",
 | 
			
		||||
    "eslint-plugin-eslint-comments": "^3.2.0",
 | 
			
		||||
    "eslint-plugin-jest": "^24.4.0",
 | 
			
		||||
    "eslint-plugin-prettier": "^3.3.1",
 | 
			
		||||
    "eslint-plugin-jest": "^27.0.4",
 | 
			
		||||
    "eslint-plugin-prettier": "^4.2.1",
 | 
			
		||||
    "eslint-plugin-react": "^7.22.0",
 | 
			
		||||
    "eslint-plugin-react-hooks": "^4.2.0",
 | 
			
		||||
    "jest-junit": "^12.0.0",
 | 
			
		||||
    "jest-junit": "^14.0.1",
 | 
			
		||||
    "prettier": "^2.3.2",
 | 
			
		||||
    "prettier-config": "^1.0.0",
 | 
			
		||||
    "react-scripts": "4.0.3"
 | 
			
		||||
    "react-scripts": "5.0.1"
 | 
			
		||||
  },
 | 
			
		||||
  "apidoc": {
 | 
			
		||||
    "name": "OpenMediaCenter",
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,10 @@
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navitem-active {
 | 
			
		||||
    opacity: 0.85;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navitem:hover {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/App.tsx
									
									
									
									
									
								
							@@ -10,7 +10,7 @@ import style from './App.module.css';
 | 
			
		||||
import SettingsPage from './pages/SettingsPage/SettingsPage';
 | 
			
		||||
import CategoryPage from './pages/CategoryPage/CategoryPage';
 | 
			
		||||
 | 
			
		||||
import {NavLink, Route, Switch, useRouteMatch} from 'react-router-dom';
 | 
			
		||||
import {NavLink, Route, Routes} from 'react-router-dom';
 | 
			
		||||
import Player from './pages/Player/Player';
 | 
			
		||||
import ActorOverviewPage from './pages/ActorOverviewPage/ActorOverviewPage';
 | 
			
		||||
import ActorPage from './pages/ActorPage/ActorPage';
 | 
			
		||||
@@ -47,7 +47,7 @@ class App extends React.Component<{}, state> {
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <LoginContextProvider>
 | 
			
		||||
                <Switch>
 | 
			
		||||
                <Routes>
 | 
			
		||||
                    <Route path='/login'>
 | 
			
		||||
                        <AuthenticationPage />
 | 
			
		||||
                    </Route>
 | 
			
		||||
@@ -55,13 +55,19 @@ class App extends React.Component<{}, state> {
 | 
			
		||||
                        {this.navBar()}
 | 
			
		||||
                        <MyRouter />
 | 
			
		||||
                    </Route>
 | 
			
		||||
                </Switch>
 | 
			
		||||
                </Routes>
 | 
			
		||||
            </LoginContextProvider>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static contextType = FeatureContext;
 | 
			
		||||
 | 
			
		||||
    activeTab(history, path) {
 | 
			
		||||
        if (history.location.pathname === path) {
 | 
			
		||||
            return { color: "red" };
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * render the top navigation bar
 | 
			
		||||
     */
 | 
			
		||||
@@ -75,39 +81,34 @@ class App extends React.Component<{}, state> {
 | 
			
		||||
                    Home
 | 
			
		||||
                </NavLink>
 | 
			
		||||
                <NavLink
 | 
			
		||||
                    className={[style.navitem, themeStyle.navitem].join(' ')}
 | 
			
		||||
                    to={'/media/random'}
 | 
			
		||||
                    activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                    className={({ isActive }) => [style.navitem, themeStyle.navitem, (isActive ? 'navitem-active' : '')].join(' ')}>
 | 
			
		||||
                    Random Video
 | 
			
		||||
                </NavLink>
 | 
			
		||||
 | 
			
		||||
                <NavLink
 | 
			
		||||
                    className={[style.navitem, themeStyle.navitem].join(' ')}
 | 
			
		||||
                    to={'/media/categories'}
 | 
			
		||||
                    activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                    className={({ isActive }) => [style.navitem, themeStyle.navitem, (isActive ? 'navitem-active' : '')].join(' ')}
 | 
			
		||||
                    to={'/media/categories'}>
 | 
			
		||||
                    Categories
 | 
			
		||||
                </NavLink>
 | 
			
		||||
 | 
			
		||||
                <NavLink
 | 
			
		||||
                    className={[style.navitem, themeStyle.navitem].join(' ')}
 | 
			
		||||
                    to={'/media/actors'}
 | 
			
		||||
                    activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                    className={({ isActive }) => [style.navitem, themeStyle.navitem, (isActive ? 'navitem-active' : '')].join(' ')}
 | 
			
		||||
                    to={'/media/actors'}>
 | 
			
		||||
                    Actors
 | 
			
		||||
                </NavLink>
 | 
			
		||||
 | 
			
		||||
                {this.context.TVShowEnabled ? (
 | 
			
		||||
                    <NavLink
 | 
			
		||||
                        className={[style.navitem, themeStyle.navitem].join(' ')}
 | 
			
		||||
                        to={'/media/tvshows'}
 | 
			
		||||
                        activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                        className={({ isActive }) => [style.navitem, themeStyle.navitem, (isActive ? 'navitem-active' : '')].join(' ')}
 | 
			
		||||
                        to={'/media/tvshows'}>
 | 
			
		||||
                        TV Shows
 | 
			
		||||
                    </NavLink>
 | 
			
		||||
                ) : null}
 | 
			
		||||
 | 
			
		||||
                <NavLink
 | 
			
		||||
                    className={[style.navitem, themeStyle.navitem].join(' ')}
 | 
			
		||||
                    to={'/media/settings'}
 | 
			
		||||
                    activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                    className={({ isActive }) => [style.navitem, themeStyle.navitem, (isActive ? 'navitem-active' : '')].join(' ')}
 | 
			
		||||
                    to={'/media/settings'}>
 | 
			
		||||
                    Settings
 | 
			
		||||
                </NavLink>
 | 
			
		||||
            </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import React, {PropsWithChildren} from 'react';
 | 
			
		||||
import style from './SideBar.module.css';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
 | 
			
		||||
interface SideBarProps {
 | 
			
		||||
interface SideBarProps extends  PropsWithChildren{
 | 
			
		||||
    hiddenFrame?: boolean;
 | 
			
		||||
    width?: string;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,9 +9,8 @@ import {TagType} from '../../types/VideoTypes';
 | 
			
		||||
import {VideoTypes} from '../../types/ApiTypes';
 | 
			
		||||
import {addKeyHandler, removeKeyHandler} from '../../utils/ShortkeyHandler';
 | 
			
		||||
import {IconButton} from '../../elements/GPElements/Button';
 | 
			
		||||
import {faPlusCircle} from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import {faMinusCircle, faPlusCircle} from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import AddTagPopup from '../../elements/Popups/AddTagPopup/AddTagPopup';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
 | 
			
		||||
interface state {
 | 
			
		||||
    videos: VideoTypes.VideoUnloadedType[];
 | 
			
		||||
@@ -29,6 +28,8 @@ interface GetRandomMoviesType {
 | 
			
		||||
 * Randompage shuffles random viedeopreviews and provides a shuffle btn
 | 
			
		||||
 */
 | 
			
		||||
class RandomPage extends React.Component<{}, state> {
 | 
			
		||||
    readonly LoadNR = 3;
 | 
			
		||||
 | 
			
		||||
    constructor(props: {}) {
 | 
			
		||||
        super(props);
 | 
			
		||||
 | 
			
		||||
@@ -45,7 +46,7 @@ class RandomPage extends React.Component<{}, state> {
 | 
			
		||||
    componentDidMount(): void {
 | 
			
		||||
        addKeyHandler(this.keypress);
 | 
			
		||||
 | 
			
		||||
        this.loadShuffledvideos();
 | 
			
		||||
        this.loadShuffledvideos(this.LoadNR);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentWillUnmount(): void {
 | 
			
		||||
@@ -55,7 +56,7 @@ class RandomPage extends React.Component<{}, state> {
 | 
			
		||||
    render(): JSX.Element {
 | 
			
		||||
        return (
 | 
			
		||||
            <div>
 | 
			
		||||
                <PageTitle title='Random Videos' subtitle={GlobalInfos.getRandomNR() + 'pc'} />
 | 
			
		||||
                <PageTitle title='Random Videos' subtitle='3pc' />
 | 
			
		||||
 | 
			
		||||
                <SideBar>
 | 
			
		||||
                    <SideBarTitle>Visible Tags:</SideBarTitle>
 | 
			
		||||
@@ -74,11 +75,24 @@ class RandomPage extends React.Component<{}, state> {
 | 
			
		||||
                            this.setState({addTagPopup: true});
 | 
			
		||||
                        }}
 | 
			
		||||
                    />
 | 
			
		||||
                    {this.state.filterTags.length > 0 ? (
 | 
			
		||||
                        <IconButton
 | 
			
		||||
                            title='Remove All'
 | 
			
		||||
                            icon={faMinusCircle}
 | 
			
		||||
                            onClick={(): void => {
 | 
			
		||||
                                this.setState({filterTags: []}, (): void => {
 | 
			
		||||
                                    this.loadShuffledvideos(this.LoadNR);
 | 
			
		||||
                                });
 | 
			
		||||
                            }}
 | 
			
		||||
                        />
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <></>
 | 
			
		||||
                    )}
 | 
			
		||||
                </SideBar>
 | 
			
		||||
 | 
			
		||||
                {this.state.videos.length !== 0 ? <VideoContainer data={this.state.videos} /> : <div>No Data found!</div>}
 | 
			
		||||
                <div className={style.Shufflebutton}>
 | 
			
		||||
                    <button onClick={(): void => this.loadShuffledvideos()} className={style.btnshuffle}>
 | 
			
		||||
                    <button onClick={(): void => this.loadShuffledvideos(this.LoadNR)} className={style.btnshuffle}>
 | 
			
		||||
                        Shuffle
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -87,7 +101,7 @@ class RandomPage extends React.Component<{}, state> {
 | 
			
		||||
                        onHide={(): void => this.setState({addTagPopup: false})}
 | 
			
		||||
                        submit={(tagId: number, tagName: string): void => {
 | 
			
		||||
                            this.setState({filterTags: [...this.state.filterTags, {TagId: tagId, TagName: tagName}]}, (): void => {
 | 
			
		||||
                                this.loadShuffledvideos();
 | 
			
		||||
                                this.loadShuffledvideos(this.LoadNR);
 | 
			
		||||
                            });
 | 
			
		||||
                        }}
 | 
			
		||||
                    />
 | 
			
		||||
@@ -98,15 +112,12 @@ class RandomPage extends React.Component<{}, state> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * load random videos from backend
 | 
			
		||||
     * @param nr number of videos to load
 | 
			
		||||
     */
 | 
			
		||||
    loadShuffledvideos(): void {
 | 
			
		||||
    loadShuffledvideos(nr: number): void {
 | 
			
		||||
        callAPI<GetRandomMoviesType>(
 | 
			
		||||
            APINode.Video,
 | 
			
		||||
            {
 | 
			
		||||
                action: 'getRandomMovies',
 | 
			
		||||
                Number: GlobalInfos.getRandomNR(),
 | 
			
		||||
                TagFilter: this.state.filterTags.map((t) => t.TagId)
 | 
			
		||||
            },
 | 
			
		||||
            {action: 'getRandomMovies', Number: nr, TagFilter: this.state.filterTags.map((t) => t.TagId)},
 | 
			
		||||
            (result) => {
 | 
			
		||||
                this.setState({videos: []}); // needed to trigger rerender of main videoview
 | 
			
		||||
                this.setState({
 | 
			
		||||
@@ -124,7 +135,7 @@ class RandomPage extends React.Component<{}, state> {
 | 
			
		||||
    private keypress(event: KeyboardEvent): void {
 | 
			
		||||
        // bind s to shuffle
 | 
			
		||||
        if (event.key === 's') {
 | 
			
		||||
            this.loadShuffledvideos();
 | 
			
		||||
            this.loadShuffledvideos(3);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,8 +33,7 @@ class GeneralSettings extends React.Component<Props, state> {
 | 
			
		||||
                Password: '',
 | 
			
		||||
                PasswordEnabled: false,
 | 
			
		||||
                TMDBGrabbing: false,
 | 
			
		||||
                VideoPath: '',
 | 
			
		||||
                RandomNR: 3
 | 
			
		||||
                VideoPath: ''
 | 
			
		||||
            },
 | 
			
		||||
            sizes: {
 | 
			
		||||
                DBSize: 0,
 | 
			
		||||
@@ -200,23 +199,6 @@ class GeneralSettings extends React.Component<Props, state> {
 | 
			
		||||
                            />
 | 
			
		||||
                        </Form.Group>
 | 
			
		||||
 | 
			
		||||
                        <Form.Group className={style.mediacenternameform} data-testid='randnrform'>
 | 
			
		||||
                            <Form.Label>Number of random videos on Random page</Form.Label>
 | 
			
		||||
                            <Form.Control
 | 
			
		||||
                                type='number'
 | 
			
		||||
                                placeholder='2'
 | 
			
		||||
                                value={this.state.generalSettings.RandomNR}
 | 
			
		||||
                                onChange={(e): void =>
 | 
			
		||||
                                    this.setState({
 | 
			
		||||
                                        generalSettings: {
 | 
			
		||||
                                            ...this.state.generalSettings,
 | 
			
		||||
                                            RandomNR: parseInt(e.target.value, 10)
 | 
			
		||||
                                        }
 | 
			
		||||
                                    })
 | 
			
		||||
                                }
 | 
			
		||||
                            />
 | 
			
		||||
                        </Form.Group>
 | 
			
		||||
 | 
			
		||||
                        <Button variant='primary' type='submit'>
 | 
			
		||||
                            Submit
 | 
			
		||||
                        </Button>
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ export class TVPlayer extends React.Component<Props, State> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private closebtn(): void {
 | 
			
		||||
        this.props.history.goBack();
 | 
			
		||||
        this.props.goBack();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,11 @@ import Preview from '../../elements/Preview/Preview';
 | 
			
		||||
import {APINode, callAPI, callAPIPlain} from '../../utils/Api';
 | 
			
		||||
import {TVShow} from '../../types/ApiTypes';
 | 
			
		||||
import DynamicContentContainer from '../../elements/DynamicContentContainer/DynamicContentContainer';
 | 
			
		||||
import {Route, Switch, useRouteMatch} from 'react-router-dom';
 | 
			
		||||
import {Route, Routes, useMatch} from 'react-router-dom';
 | 
			
		||||
import EpisodePage from './EpisodePage';
 | 
			
		||||
import PageTitle, {Line} from '../../elements/PageTitle/PageTitle';
 | 
			
		||||
import SideBar, {SideBarItem, SideBarTitle} from '../../elements/SideBar/SideBar';
 | 
			
		||||
import {useLocation} from "react-router";
 | 
			
		||||
 | 
			
		||||
interface State {
 | 
			
		||||
    loading: boolean;
 | 
			
		||||
@@ -55,7 +56,7 @@ export class TVShowPage extends React.Component<Props, State> {
 | 
			
		||||
                                        (result) => callback(result)
 | 
			
		||||
                                    );
 | 
			
		||||
                                }}
 | 
			
		||||
                                linkPath={'/tvshows/' + elem.Id}
 | 
			
		||||
                                linkPath={'/media/tvshows/' + elem.Id}
 | 
			
		||||
                            />
 | 
			
		||||
                        )}
 | 
			
		||||
                        data={this.state.loading ? [] : this.data}
 | 
			
		||||
@@ -68,16 +69,16 @@ export class TVShowPage extends React.Component<Props, State> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function (): JSX.Element {
 | 
			
		||||
    let match = useRouteMatch();
 | 
			
		||||
    const { pathname } = useLocation();
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Switch>
 | 
			
		||||
            <Route exact path={`${match.path}/:id`}>
 | 
			
		||||
        <Routes>
 | 
			
		||||
            <Route path={`${pathname}/:id`}>
 | 
			
		||||
                <EpisodePage />
 | 
			
		||||
            </Route>
 | 
			
		||||
            <Route path={match.path}>
 | 
			
		||||
            <Route path={pathname}>
 | 
			
		||||
                <TVShowPage />
 | 
			
		||||
            </Route>
 | 
			
		||||
        </Switch>
 | 
			
		||||
        </Routes>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,6 @@ export namespace SettingsTypes {
 | 
			
		||||
        TVShowPath: string;
 | 
			
		||||
        TVShowEnabled: boolean;
 | 
			
		||||
        FullDeleteEnabled: boolean;
 | 
			
		||||
        RandomNR: number;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    export interface SettingsType {
 | 
			
		||||
@@ -51,7 +50,6 @@ export namespace SettingsTypes {
 | 
			
		||||
        PasswordEnabled: boolean;
 | 
			
		||||
        TMDBGrabbing: boolean;
 | 
			
		||||
        DarkMode: boolean;
 | 
			
		||||
        RandomNR: number;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    export interface SizesType {
 | 
			
		||||
 
 | 
			
		||||
@@ -78,8 +78,8 @@ function generalAPICall<T>(
 | 
			
		||||
                // decode json or text
 | 
			
		||||
                const data = json ? await response.json() : await response.text();
 | 
			
		||||
                callback(data);
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                errorcallback(e);
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                errorcallback(e.toString());
 | 
			
		||||
            }
 | 
			
		||||
        } else if (response.status === 400) {
 | 
			
		||||
            // Bad Request --> invalid token
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@ class StaticInfos {
 | 
			
		||||
    private tvshowpath: string = '';
 | 
			
		||||
    private TVShowsEnabled: boolean = false;
 | 
			
		||||
    private fullDeleteable: boolean = false;
 | 
			
		||||
    private RandomNR: number = 3;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * check if the current theme is the dark theme
 | 
			
		||||
@@ -45,7 +44,6 @@ class StaticInfos {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handlers: (() => void)[] = [];
 | 
			
		||||
 | 
			
		||||
    onThemeChange(func: () => void): void {
 | 
			
		||||
        this.handlers.push(func);
 | 
			
		||||
    }
 | 
			
		||||
@@ -73,14 +71,6 @@ class StaticInfos {
 | 
			
		||||
    getTVShowPath(): string {
 | 
			
		||||
        return this.tvshowpath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setRandomNR(nr: number): void {
 | 
			
		||||
        this.RandomNR = nr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getRandomNR(): number {
 | 
			
		||||
        return this.RandomNR;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new StaticInfos();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import React, {FunctionComponent, useState} from 'react';
 | 
			
		||||
import React, {FunctionComponent, PropsWithChildren, useState} from 'react';
 | 
			
		||||
 | 
			
		||||
export interface FeatureContextType {
 | 
			
		||||
    setTVShowEnabled: (enabled: boolean) => void;
 | 
			
		||||
@@ -17,7 +17,7 @@ export const FeatureContext = React.createContext<FeatureContextType>({
 | 
			
		||||
    VideosFullyDeleteable: false
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const FeatureContextProvider: FunctionComponent = (props): JSX.Element => {
 | 
			
		||||
export const FeatureContextProvider: FunctionComponent<PropsWithChildren> = (props): JSX.Element => {
 | 
			
		||||
    const [tvshowenabled, settvshowenabled] = useState(false);
 | 
			
		||||
    const [fullydeletablevids, setfullydeleteable] = useState(false);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
import {LoginContext, LoginPerm, LoginState} from './LoginContext';
 | 
			
		||||
import React, {FunctionComponent, useContext, useEffect, useState} from 'react';
 | 
			
		||||
import {useHistory, useLocation} from 'react-router';
 | 
			
		||||
import React, {FunctionComponent, PropsWithChildren, useContext, useEffect, useState} from 'react';
 | 
			
		||||
import {useLocation, useNavigate} from 'react-router';
 | 
			
		||||
import {cookie} from './Cookie';
 | 
			
		||||
import {APINode, callAPI} from '../Api';
 | 
			
		||||
import {SettingsTypes} from '../../types/ApiTypes';
 | 
			
		||||
import GlobalInfos from '../GlobalInfos';
 | 
			
		||||
import {FeatureContext} from './FeatureContext';
 | 
			
		||||
 | 
			
		||||
export const LoginContextProvider: FunctionComponent = (props): JSX.Element => {
 | 
			
		||||
export const LoginContextProvider: FunctionComponent<PropsWithChildren> = (props): JSX.Element => {
 | 
			
		||||
    let initialLoginState = LoginState.LoggedIn;
 | 
			
		||||
    let initialUserPerm = LoginPerm.User;
 | 
			
		||||
 | 
			
		||||
@@ -32,7 +32,6 @@ export const LoginContextProvider: FunctionComponent = (props): JSX.Element => {
 | 
			
		||||
                GlobalInfos.enableDarkTheme(result.DarkMode);
 | 
			
		||||
 | 
			
		||||
                GlobalInfos.setVideoPaths(result.VideoPath, result.TVShowPath);
 | 
			
		||||
                GlobalInfos.setRandomNR(result.RandomNR);
 | 
			
		||||
 | 
			
		||||
                features.setTVShowEnabled(result.TVShowEnabled);
 | 
			
		||||
                features.setVideosFullyDeleteable(result.FullDeleteEnabled);
 | 
			
		||||
@@ -51,7 +50,7 @@ export const LoginContextProvider: FunctionComponent = (props): JSX.Element => {
 | 
			
		||||
        );
 | 
			
		||||
    }, [features, loginState]);
 | 
			
		||||
 | 
			
		||||
    const hist = useHistory();
 | 
			
		||||
    const navigate = useNavigate();
 | 
			
		||||
    const loc = useLocation();
 | 
			
		||||
 | 
			
		||||
    // trigger redirect on loginstate change
 | 
			
		||||
@@ -60,14 +59,14 @@ export const LoginContextProvider: FunctionComponent = (props): JSX.Element => {
 | 
			
		||||
            // if we arent already in dashboard tree we want to redirect to default dashboard page
 | 
			
		||||
            console.log('redirecting to dashboard' + loc.pathname);
 | 
			
		||||
            if (!loc.pathname.startsWith('/media')) {
 | 
			
		||||
                hist.replace('/media');
 | 
			
		||||
                navigate('/media');
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (!loc.pathname.startsWith('/login')) {
 | 
			
		||||
                hist.replace('/login');
 | 
			
		||||
                navigate('/login');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }, [hist, loc.pathname, loginState]);
 | 
			
		||||
    }, [navigate, loc.pathname, loginState]);
 | 
			
		||||
 | 
			
		||||
    const value = {
 | 
			
		||||
        logout: (): void => {
 | 
			
		||||
@@ -87,7 +86,7 @@ export const LoginContextProvider: FunctionComponent = (props): JSX.Element => {
 | 
			
		||||
    return <LoginContext.Provider value={value}>{props.children}</LoginContext.Provider>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
interface Props extends PropsWithChildren{
 | 
			
		||||
    perm: LoginPerm;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user