Track watch activity for scenes. (#3055)

* track watchtime and view time
* add view count sorting, added continue position filter
* display metrics in file info
* add toggle for tracking activity
* save activity every 10 seconds
* reset resume when video is nearly complete
* start from beginning when playing scene in queue

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
CJ
2022-11-20 19:55:15 -06:00
committed by GitHub
parent f39fa416a9
commit 0664c5b974
42 changed files with 1239 additions and 104 deletions

View File

@@ -72,21 +72,25 @@ func loadSceneRelationships(ctx context.Context, expected models.Scene, actual *
func Test_sceneQueryBuilder_Create(t *testing.T) {
var (
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 60
ocounter = 5
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
sceneIndex = 123
sceneIndex2 = 234
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
stashID1 = "stashid1"
stashID2 = "stashid2"
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 60
ocounter = 5
lastPlayedAt = time.Date(2002, 1, 1, 0, 0, 0, 0, time.UTC)
resumeTime = 10.0
playCount = 3
playDuration = 34.0
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
sceneIndex = 123
sceneIndex2 = 234
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
stashID1 = "stashid1"
stashID2 = "stashid2"
date = models.NewDate("2003-02-01")
@@ -136,6 +140,10 @@ func Test_sceneQueryBuilder_Create(t *testing.T) {
Endpoint: endpoint2,
},
}),
LastPlayedAt: &lastPlayedAt,
ResumeTime: float64(resumeTime),
PlayCount: playCount,
PlayDuration: playDuration,
},
false,
},
@@ -180,6 +188,10 @@ func Test_sceneQueryBuilder_Create(t *testing.T) {
Endpoint: endpoint2,
},
}),
LastPlayedAt: &lastPlayedAt,
ResumeTime: resumeTime,
PlayCount: playCount,
PlayDuration: playDuration,
},
false,
},
@@ -299,21 +311,25 @@ func makeSceneFileWithID(i int) *file.VideoFile {
func Test_sceneQueryBuilder_Update(t *testing.T) {
var (
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 60
ocounter = 5
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
sceneIndex = 123
sceneIndex2 = 234
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
stashID1 = "stashid1"
stashID2 = "stashid2"
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 60
ocounter = 5
lastPlayedAt = time.Date(2002, 1, 1, 0, 0, 0, 0, time.UTC)
resumeTime = 10.0
playCount = 3
playDuration = 34.0
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
sceneIndex = 123
sceneIndex2 = 234
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
stashID1 = "stashid1"
stashID2 = "stashid2"
date = models.NewDate("2003-02-01")
)
@@ -362,6 +378,10 @@ func Test_sceneQueryBuilder_Update(t *testing.T) {
Endpoint: endpoint2,
},
}),
LastPlayedAt: &lastPlayedAt,
ResumeTime: resumeTime,
PlayCount: playCount,
PlayDuration: playDuration,
},
false,
},
@@ -507,21 +527,25 @@ func clearScenePartial() models.ScenePartial {
func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) {
var (
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 60
ocounter = 5
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
sceneIndex = 123
sceneIndex2 = 234
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
stashID1 = "stashid1"
stashID2 = "stashid2"
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 60
ocounter = 5
lastPlayedAt = time.Date(2002, 1, 1, 0, 0, 0, 0, time.UTC)
resumeTime = 10.0
playCount = 3
playDuration = 34.0
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
sceneIndex = 123
sceneIndex2 = 234
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
stashID1 = "stashid1"
stashID2 = "stashid2"
date = models.NewDate("2003-02-01")
)
@@ -587,6 +611,10 @@ func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) {
},
Mode: models.RelationshipUpdateModeSet,
},
LastPlayedAt: models.NewOptionalTime(lastPlayedAt),
ResumeTime: models.NewOptionalFloat64(resumeTime),
PlayCount: models.NewOptionalInt(playCount),
PlayDuration: models.NewOptionalFloat64(playDuration),
},
models.Scene{
ID: sceneIDs[sceneIdxWithSpacedName],
@@ -628,6 +656,10 @@ func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) {
Endpoint: endpoint2,
},
}),
LastPlayedAt: &lastPlayedAt,
ResumeTime: resumeTime,
PlayCount: playCount,
PlayDuration: playDuration,
},
false,
},
@@ -2088,6 +2120,45 @@ func TestSceneQuery(t *testing.T) {
excludeIdxs []int
wantErr bool
}{
{
"specific resume time",
nil,
&models.SceneFilterType{
ResumeTime: &models.IntCriterionInput{
Modifier: models.CriterionModifierEquals,
Value: int(getSceneResumeTime(sceneIdxWithGallery)),
},
},
[]int{sceneIdxWithGallery},
[]int{sceneIdxWithMovie},
false,
},
{
"specific play duration",
nil,
&models.SceneFilterType{
PlayDuration: &models.IntCriterionInput{
Modifier: models.CriterionModifierEquals,
Value: int(getScenePlayDuration(sceneIdxWithGallery)),
},
},
[]int{sceneIdxWithGallery},
[]int{sceneIdxWithMovie},
false,
},
{
"specific play count",
nil,
&models.SceneFilterType{
PlayCount: &models.IntCriterionInput{
Modifier: models.CriterionModifierEquals,
Value: getScenePlayCount(sceneIdxWithGallery),
},
},
[]int{sceneIdxWithGallery},
[]int{sceneIdxWithMovie},
false,
},
{
"stash id with endpoint",
nil,
@@ -3697,6 +3768,34 @@ func TestSceneQuerySorting(t *testing.T) {
-1,
-1,
},
{
"play_count",
"play_count",
models.SortDirectionEnumDesc,
sceneIDs[sceneIdx1WithPerformer],
-1,
},
{
"last_played_at",
"last_played_at",
models.SortDirectionEnumDesc,
sceneIDs[sceneIdx1WithPerformer],
-1,
},
{
"resume_time",
"resume_time",
models.SortDirectionEnumDesc,
sceneIDs[sceneIdx1WithPerformer],
-1,
},
{
"play_duration",
"play_duration",
models.SortDirectionEnumDesc,
sceneIDs[sceneIdx1WithPerformer],
-1,
},
}
qb := db.Scene
@@ -4245,5 +4344,154 @@ func TestSceneStore_AssignFiles(t *testing.T) {
}
}
func TestSceneStore_IncrementWatchCount(t *testing.T) {
tests := []struct {
name string
sceneID int
expectedCount int
wantErr bool
}{
{
"valid",
sceneIDs[sceneIdx1WithPerformer],
getScenePlayCount(sceneIdx1WithPerformer) + 1,
false,
},
{
"invalid scene id",
invalidID,
0,
true,
},
}
qb := db.Scene
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
withRollbackTxn(func(ctx context.Context) error {
newVal, err := qb.IncrementWatchCount(ctx, tt.sceneID)
if (err != nil) != tt.wantErr {
t.Errorf("SceneStore.IncrementWatchCount() error = %v, wantErr %v", err, tt.wantErr)
}
if err != nil {
return nil
}
assert := assert.New(t)
assert.Equal(tt.expectedCount, newVal)
// find the scene and check the count
scene, err := qb.Find(ctx, tt.sceneID)
if err != nil {
t.Errorf("SceneStore.Find() error = %v", err)
}
assert.Equal(tt.expectedCount, scene.PlayCount)
assert.True(scene.LastPlayedAt.After(time.Now().Add(-1 * time.Minute)))
return nil
})
})
}
}
func TestSceneStore_SaveActivity(t *testing.T) {
var (
resumeTime = 111.2
playDuration = 98.7
)
tests := []struct {
name string
sceneIdx int
resumeTime *float64
playDuration *float64
wantErr bool
}{
{
"both",
sceneIdx1WithPerformer,
&resumeTime,
&playDuration,
false,
},
{
"resumeTime only",
sceneIdx1WithPerformer,
&resumeTime,
nil,
false,
},
{
"playDuration only",
sceneIdx1WithPerformer,
nil,
&playDuration,
false,
},
{
"none",
sceneIdx1WithPerformer,
nil,
nil,
false,
},
{
"invalid scene id",
-1,
&resumeTime,
&playDuration,
true,
},
}
qb := db.Scene
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
withRollbackTxn(func(ctx context.Context) error {
id := -1
if tt.sceneIdx != -1 {
id = sceneIDs[tt.sceneIdx]
}
_, err := qb.SaveActivity(ctx, id, tt.resumeTime, tt.playDuration)
if (err != nil) != tt.wantErr {
t.Errorf("SceneStore.SaveActivity() error = %v, wantErr %v", err, tt.wantErr)
}
if err != nil {
return nil
}
assert := assert.New(t)
// find the scene and check the values
scene, err := qb.Find(ctx, id)
if err != nil {
t.Errorf("SceneStore.Find() error = %v", err)
}
expectedResumeTime := getSceneResumeTime(tt.sceneIdx)
expectedPlayDuration := getScenePlayDuration(tt.sceneIdx)
if tt.resumeTime != nil {
expectedResumeTime = *tt.resumeTime
}
if tt.playDuration != nil {
expectedPlayDuration += *tt.playDuration
}
assert.Equal(expectedResumeTime, scene.ResumeTime)
assert.Equal(expectedPlayDuration, scene.PlayDuration)
return nil
})
})
}
}
// TODO Count
// TODO SizeCount