mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user