diff --git a/.gitignore b/.gitignore index fd05ff7cf..a667c4814 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ bin .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf +.idea/copilot/ # Generated files .idea/**/contentModel.xml diff --git a/.golangci.yml b/.golangci.yml index 0ccde4405..37693758f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -27,9 +27,9 @@ linters: run: go: '1.21' timeout: 10m - skip-dirs: - - node_modules - - template +# skip-dirs: +# - node_modules +# - template linters-settings: stylecheck: @@ -67,14 +67,17 @@ linters-settings: - name: modifies-value-receiver gofumpt: extra-rules: false - lang-version: "1.21" + #lang-version: "1.21" issues: max-issues-per-linter: 0 max-same-issues: 0 new: true new-from-rev: dev - fix: false + fix: false + exclude-dirs: + - node_modules + - template exclude-rules: # Exclude some linters from running on tests files. - path: _test\.go diff --git a/.idea/.gitignore b/.idea/.gitignore index 0e820690b..930678c0c 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -7,3 +7,5 @@ /dataSources.local.xml # Editor-based HTTP Client requests /httpRequests/ +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/api/info-pages.go b/api/info-pages.go index e4b0acc9a..e917060fe 100644 --- a/api/info-pages.go +++ b/api/info-pages.go @@ -58,7 +58,6 @@ func (r infoPageRoutes) updateText(c *gin.Context) { RawContent: reqBody.RawContent, Type: reqBody.Type, }) - if err != nil { _ = c.Error(tools.RequestError{ Status: http.StatusInternalServerError, diff --git a/api/progress.go b/api/progress.go index 134881744..6029bcb2c 100644 --- a/api/progress.go +++ b/api/progress.go @@ -2,7 +2,9 @@ package api import ( "errors" + "math" "net/http" + "slices" "strconv" "sync" "time" @@ -114,10 +116,40 @@ func (r progressRoutes) saveProgress(c *gin.Context) { }) return } + + stream, err := r.DaoWrapper.StreamsDao.GetStreamByID(c, strconv.FormatUint(uint64(request.StreamID), 10)) + if err != nil { + return + } + + watchedToLastSilence := false + + // logger.Debug("Save progress") + duration := stream.Duration.Int32 + if duration == 0 { + dur := stream.End.Sub(stream.Start) + duration += int32(dur.Seconds()) + int32(dur.Minutes())*60 + int32(dur.Minutes())*60*60 + } + // logger.Debug("Duration", "duration", duration) + if duration != 0 && len(stream.Silences) > 0 { + lastSilence := slices.MaxFunc(stream.Silences, func(silence model.Silence, other model.Silence) int { + return int(silence.End) - int(other.End) + }) + + // Add a little wiggle time to the end if ffmpeg didn't detect the silence till the end + if math.Abs(float64(lastSilence.End-uint(duration))) < 10 { + lastSilencePercent := float64(lastSilence.Start) / float64(duration) + if request.Progress >= lastSilencePercent { + watchedToLastSilence = true + } + } + } + progressBuff.add(model.StreamProgress{ Progress: request.Progress, StreamID: request.StreamID, UserID: tumLiveContext.User.ID, + Watched: request.Progress > .9 || watchedToLastSilence, }) } diff --git a/api/progress_test.go b/api/progress_test.go index 4ffb31c6a..bb8a9d8d0 100644 --- a/api/progress_test.go +++ b/api/progress_test.go @@ -1,6 +1,7 @@ package api import ( + "database/sql" "errors" "fmt" "net/http" @@ -29,7 +30,7 @@ func TestProgressReport(t *testing.T) { req := progressRequest{ StreamID: uint(1), - Progress: 0, + Progress: float64(.5), } gomino.TestCases{ @@ -51,7 +52,19 @@ func TestProgressReport(t *testing.T) { ExpectedCode: http.StatusForbidden, }, "success": { - Router: ProgressRouterWrapper, + Router: func(r *gin.Engine) { + wrapper := dao.DaoWrapper{ + StreamsDao: func() dao.StreamsDao { + streamsMock := mock_dao.NewMockStreamsDao(gomock.NewController(t)) + streamsMock. + EXPECT(). + GetStreamByID(gomock.Any(), "1"). + Return(model.Stream{Duration: sql.NullInt32{Int32: int32(20)}}, nil) + return streamsMock + }(), + } + configProgressRouter(r, wrapper) + }, Body: req, Middlewares: testutils.GetMiddlewares(tools.ErrorHandler, testutils.TUMLiveContext(testutils.TUMLiveContextStudent)), ExpectedCode: http.StatusOK, diff --git a/dao/progress.go b/dao/progress.go index 6a73c07b6..10273a249 100644 --- a/dao/progress.go +++ b/dao/progress.go @@ -30,12 +30,41 @@ func (d progressDao) GetProgressesForUser(userID uint) (r []model.StreamProgress return r, DB.Where("user_id = ?", userID).Find(&r).Error } +func filterProgress(progresses []model.StreamProgress, watched bool) []model.StreamProgress { + var result []model.StreamProgress + for _, progress := range progresses { + if progress.Watched == watched { + result = append(result, progress) + } + } + return result +} + // SaveProgresses saves a slice of stream progresses. If a progress already exists, it will be updated. +// We need two different methods for that because else the watched state will be overwritten. func (d progressDao) SaveProgresses(progresses []model.StreamProgress) error { - return DB.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "stream_id"}, {Name: "user_id"}}, // key column - DoUpdates: clause.AssignmentColumns([]string{"progress"}), // column needed to be updated - }).Create(progresses).Error + noWatched := filterProgress(progresses, false) + watched := filterProgress(progresses, true) + var err error + if len(noWatched) > 0 { + err = DB.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "stream_id"}, {Name: "user_id"}}, // key column + DoUpdates: clause.AssignmentColumns([]string{"progress"}), // column needed to be updated + }).Create(noWatched).Error + } + var err2 error + + if len(watched) > 0 { + err2 = DB.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "stream_id"}, {Name: "user_id"}}, // key column + DoUpdates: clause.AssignmentColumns([]string{"progress", "watched"}), // column needed to be updated + }).Create(watched).Error + } + + if err != nil { + return err + } + return err2 } // SaveWatchedState creates/updates a stream progress with its corresponding watched state. diff --git a/tools/realtime/connector/melody.go b/tools/realtime/connector/melody.go index e4bb37351..e4dffa92c 100644 --- a/tools/realtime/connector/melody.go +++ b/tools/realtime/connector/melody.go @@ -2,6 +2,7 @@ package connector import ( "net/http" + "github.com/TUM-Dev/gocast/tools/realtime" "github.com/gabstv/melody" )