From 91bd25b5b8582ec0c37a5198d5ae672b17f53c7c Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Tue, 3 Oct 2023 07:37:14 -0400 Subject: [PATCH] [new] refactor & rename, [fix] rescaled activity view performance --- api/app-routes.go | 8 +- api/ko-routes.go | 24 +++--- book_manager.db | 0 client/syncninja.koplugin/main.lua | 4 +- database/models.go | 19 ++--- database/query.sql | 60 +++++++-------- database/query.sql.go | 118 ++++++++++++++--------------- database/schema.sql | 84 ++++++++++++++------ templates/activity.html | 2 +- templates/document-edit.html | 2 +- templates/document.html | 2 +- templates/documents.html | 6 +- templates/home.html | 11 +-- 13 files changed, 189 insertions(+), 151 deletions(-) create mode 100644 book_manager.db diff --git a/api/app-routes.go b/api/app-routes.go index d56a2f4..d9ee62e 100644 --- a/api/app-routes.go +++ b/api/app-routes.go @@ -126,12 +126,12 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any } statistics := gin.H{ - "TotalTimeLeftSeconds": (document.TotalPages - document.CurrentPage) * document.SecondsPerPage, + "TotalTimeLeftSeconds": (document.Pages - document.Page) * document.SecondsPerPage, "WordsPerMinute": "N/A", } if document.Words != nil && *document.Words != 0 { - statistics["WordsPerMinute"] = (*document.Words / document.TotalPages * document.ReadPages) / (document.TotalTimeSeconds / 60.0) + statistics["WordsPerMinute"] = (*document.Words / document.Pages * document.ReadPages) / (document.TotalTimeSeconds / 60.0) } templateVars["RelBase"] = "../" @@ -513,12 +513,12 @@ func (api *API) identifyDocument(c *gin.Context) { } statistics := gin.H{ - "TotalTimeLeftSeconds": (document.TotalPages - document.CurrentPage) * document.SecondsPerPage, + "TotalTimeLeftSeconds": (document.Pages - document.Page) * document.SecondsPerPage, "WordsPerMinute": "N/A", } if document.Words != nil && *document.Words != 0 { - statistics["WordsPerMinute"] = (*document.Words / document.TotalPages * document.ReadPages) / (document.TotalTimeSeconds / 60.0) + statistics["WordsPerMinute"] = (*document.Words / document.Pages * document.ReadPages) / (document.TotalTimeSeconds / 60.0) } templateVars["Data"] = document diff --git a/api/ko-routes.go b/api/ko-routes.go index ece43a0..634be16 100644 --- a/api/ko-routes.go +++ b/api/ko-routes.go @@ -22,11 +22,11 @@ import ( ) type activityItem struct { - DocumentID string `json:"document"` - StartTime int64 `json:"start_time"` - Duration int64 `json:"duration"` - CurrentPage int64 `json:"current_page"` - TotalPages int64 `json:"total_pages"` + DocumentID string `json:"document"` + StartTime int64 `json:"start_time"` + Duration int64 `json:"duration"` + Page int64 `json:"page"` + Pages int64 `json:"pages"` } type requestActivity struct { @@ -256,13 +256,13 @@ func (api *API) addActivities(c *gin.Context) { // Add All Activity for _, item := range rActivity.Activity { if _, err := qtx.AddActivity(api.DB.Ctx, database.AddActivityParams{ - UserID: rUser.(string), - DocumentID: item.DocumentID, - DeviceID: rActivity.DeviceID, - StartTime: time.Unix(int64(item.StartTime), 0).UTC(), - Duration: int64(item.Duration), - CurrentPage: int64(item.CurrentPage), - TotalPages: int64(item.TotalPages), + UserID: rUser.(string), + DocumentID: item.DocumentID, + DeviceID: rActivity.DeviceID, + StartTime: time.Unix(int64(item.StartTime), 0).UTC(), + Duration: int64(item.Duration), + Page: int64(item.Page), + Pages: int64(item.Pages), }); err != nil { log.Error("[addActivities] AddActivity DB Error:", err) c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Activity"}) diff --git a/book_manager.db b/book_manager.db new file mode 100644 index 0000000..e69de29 diff --git a/client/syncninja.koplugin/main.lua b/client/syncninja.koplugin/main.lua index 1fcab27..d91289c 100644 --- a/client/syncninja.koplugin/main.lua +++ b/client/syncninja.koplugin/main.lua @@ -919,8 +919,8 @@ function SyncNinja:getStatisticsActivity(timestamp) document = rows[1][i], start_time = tonumber(rows[2][i]), duration = tonumber(rows[3][i]), - current_page = tonumber(rows[4][i]), - total_pages = tonumber(rows[5][i]) + page = tonumber(rows[4][i]), + pages = tonumber(rows[5][i]) }) end diff --git a/database/models.go b/database/models.go index f199fb7..fe1f082 100644 --- a/database/models.go +++ b/database/models.go @@ -9,15 +9,15 @@ import ( ) type Activity struct { - ID int64 `json:"id"` - UserID string `json:"user_id"` - DocumentID string `json:"document_id"` - DeviceID string `json:"device_id"` - StartTime time.Time `json:"start_time"` - Duration int64 `json:"duration"` - CurrentPage int64 `json:"current_page"` - TotalPages int64 `json:"total_pages"` - CreatedAt time.Time `json:"created_at"` + ID int64 `json:"id"` + UserID string `json:"user_id"` + DocumentID string `json:"document_id"` + DeviceID string `json:"device_id"` + StartTime time.Time `json:"start_time"` + Duration int64 `json:"duration"` + Page int64 `json:"page"` + Pages int64 `json:"pages"` + CreatedAt time.Time `json:"created_at"` } type Device struct { @@ -85,6 +85,7 @@ type RescaledActivity struct { DeviceID string `json:"device_id"` UserID string `json:"user_id"` StartTime time.Time `json:"start_time"` + Pages int64 `json:"pages"` Page int64 `json:"page"` Duration int64 `json:"duration"` } diff --git a/database/query.sql b/database/query.sql index 91c8bd2..2515935 100644 --- a/database/query.sql +++ b/database/query.sql @@ -141,8 +141,8 @@ INSERT INTO activity ( device_id, start_time, duration, - current_page, - total_pages + page, + pages ) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *; @@ -192,15 +192,15 @@ WITH true_progress AS ( start_time AS last_read, SUM(duration) AS total_time_seconds, document_id, - current_page, - total_pages, + page, + pages, -- Determine Read Pages - COUNT(DISTINCT current_page) AS read_pages, + COUNT(DISTINCT page) AS read_pages, -- Derive Percentage of Book - ROUND(CAST(current_page AS REAL) / CAST(total_pages AS REAL) * 100, 2) AS percentage - FROM activity + ROUND(CAST(page AS REAL) / CAST(pages AS REAL) * 100, 2) AS percentage + FROM rescaled_activity WHERE user_id = $user_id AND document_id = $document_id GROUP BY document_id @@ -210,8 +210,8 @@ WITH true_progress AS ( SELECT documents.*, - CAST(IFNULL(current_page, 0) AS INTEGER) AS current_page, - CAST(IFNULL(total_pages, 0) AS INTEGER) AS total_pages, + CAST(IFNULL(page, 0) AS INTEGER) AS page, + CAST(IFNULL(pages, 0) AS INTEGER) AS pages, CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds, CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read, CAST(IFNULL(read_pages, 0) AS INTEGER) AS read_pages, @@ -244,9 +244,9 @@ WITH true_progress AS ( start_time AS last_read, SUM(duration) AS total_time_seconds, document_id, - current_page, - total_pages, - ROUND(CAST(current_page AS REAL) / CAST(total_pages AS REAL) * 100, 2) AS percentage + page, + pages, + ROUND(CAST(page AS REAL) / CAST(pages AS REAL) * 100, 2) AS percentage FROM activity WHERE user_id = $user_id GROUP BY document_id @@ -255,8 +255,8 @@ WITH true_progress AS ( SELECT documents.*, - CAST(IFNULL(current_page, 0) AS INTEGER) AS current_page, - CAST(IFNULL(total_pages, 0) AS INTEGER) AS total_pages, + CAST(IFNULL(page, 0) AS INTEGER) AS page, + CAST(IFNULL(pages, 0) AS INTEGER) AS pages, CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds, CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read, @@ -295,8 +295,8 @@ SELECT title, author, duration, - current_page, - total_pages + page, + pages FROM activity LEFT JOIN documents ON documents.id = activity.document_id LEFT JOIN users ON users.id = activity.user_id @@ -324,8 +324,8 @@ GROUP BY activity.device_id; -- name: GetDocumentReadStats :one SELECT - count(DISTINCT page) AS pages_read, - sum(duration) AS total_time + COUNT(DISTINCT page) AS pages_read, + SUM(duration) AS total_time FROM rescaled_activity WHERE document_id = $document_id AND user_id = $user_id @@ -333,7 +333,7 @@ AND start_time >= $start_time; -- name: GetDocumentReadStatsCapped :one WITH capped_stats AS ( - SELECT min(sum(duration), CAST($page_duration_cap AS INTEGER)) AS durations + SELECT MIN(SUM(duration), CAST($page_duration_cap AS INTEGER)) AS durations FROM rescaled_activity WHERE document_id = $document_id AND user_id = $user_id @@ -341,20 +341,20 @@ WITH capped_stats AS ( GROUP BY page ) SELECT - CAST(count(*) AS INTEGER) AS pages_read, - CAST(sum(durations) AS INTEGER) AS total_time + CAST(COUNT(*) AS INTEGER) AS pages_read, + CAST(SUM(durations) AS INTEGER) AS total_time FROM capped_stats; -- name: GetDocumentDaysRead :one WITH document_days AS ( SELECT DATE(start_time, time_offset) AS dates - FROM rescaled_activity - JOIN users ON users.id = rescaled_activity.user_id + FROM activity + JOIN users ON users.id = activity.user_id WHERE document_id = $document_id AND user_id = $user_id GROUP BY dates ) -SELECT CAST(count(*) AS INTEGER) AS days_read +SELECT CAST(COUNT(*) AS INTEGER) AS days_read FROM document_days; -- name: GetUserWindowStreaks :one @@ -381,7 +381,7 @@ partitions AS ( ), streaks AS ( SELECT - count(*) AS streak, + COUNT(*) AS streak, MIN(read_window) AS start_date, MAX(read_window) AS end_date, time_offset @@ -431,10 +431,10 @@ LIMIT 1; -- name: GetDatabaseInfo :one SELECT - (SELECT count(rowid) FROM activity WHERE activity.user_id = $user_id) AS activity_size, - (SELECT count(rowid) FROM documents) AS documents_size, - (SELECT count(rowid) FROM document_progress WHERE document_progress.user_id = $user_id) AS progress_size, - (SELECT count(rowid) FROM devices WHERE devices.user_id = $user_id) AS devices_size + (SELECT COUNT(rowid) FROM activity WHERE activity.user_id = $user_id) AS activity_size, + (SELECT COUNT(rowid) FROM documents) AS documents_size, + (SELECT COUNT(rowid) FROM document_progress WHERE document_progress.user_id = $user_id) AS progress_size, + (SELECT COUNT(rowid) FROM devices WHERE devices.user_id = $user_id) AS devices_size LIMIT 1; -- name: GetDailyReadStats :many @@ -448,7 +448,7 @@ WITH RECURSIVE last_30_days AS ( ), activity_records AS ( SELECT - sum(duration) AS seconds_read, + SUM(duration) AS seconds_read, DATE(start_time, time_offset) AS day FROM activity LEFT JOIN users ON users.id = activity.user_id diff --git a/database/query.sql.go b/database/query.sql.go index 7dc22e7..586725c 100644 --- a/database/query.sql.go +++ b/database/query.sql.go @@ -19,21 +19,21 @@ INSERT INTO activity ( device_id, start_time, duration, - current_page, - total_pages + page, + pages ) VALUES (?, ?, ?, ?, ?, ?, ?) -RETURNING id, user_id, document_id, device_id, start_time, duration, current_page, total_pages, created_at +RETURNING id, user_id, document_id, device_id, start_time, duration, page, pages, created_at ` type AddActivityParams struct { - UserID string `json:"user_id"` - DocumentID string `json:"document_id"` - DeviceID string `json:"device_id"` - StartTime time.Time `json:"start_time"` - Duration int64 `json:"duration"` - CurrentPage int64 `json:"current_page"` - TotalPages int64 `json:"total_pages"` + UserID string `json:"user_id"` + DocumentID string `json:"document_id"` + DeviceID string `json:"device_id"` + StartTime time.Time `json:"start_time"` + Duration int64 `json:"duration"` + Page int64 `json:"page"` + Pages int64 `json:"pages"` } func (q *Queries) AddActivity(ctx context.Context, arg AddActivityParams) (Activity, error) { @@ -43,8 +43,8 @@ func (q *Queries) AddActivity(ctx context.Context, arg AddActivityParams) (Activ arg.DeviceID, arg.StartTime, arg.Duration, - arg.CurrentPage, - arg.TotalPages, + arg.Page, + arg.Pages, ) var i Activity err := row.Scan( @@ -54,8 +54,8 @@ func (q *Queries) AddActivity(ctx context.Context, arg AddActivityParams) (Activ &i.DeviceID, &i.StartTime, &i.Duration, - &i.CurrentPage, - &i.TotalPages, + &i.Page, + &i.Pages, &i.CreatedAt, ) return i, err @@ -155,8 +155,8 @@ SELECT title, author, duration, - current_page, - total_pages + page, + pages FROM activity LEFT JOIN documents ON documents.id = activity.document_id LEFT JOIN users ON users.id = activity.user_id @@ -181,13 +181,13 @@ type GetActivityParams struct { } type GetActivityRow struct { - DocumentID string `json:"document_id"` - StartTime string `json:"start_time"` - Title *string `json:"title"` - Author *string `json:"author"` - Duration int64 `json:"duration"` - CurrentPage int64 `json:"current_page"` - TotalPages int64 `json:"total_pages"` + DocumentID string `json:"document_id"` + StartTime string `json:"start_time"` + Title *string `json:"title"` + Author *string `json:"author"` + Duration int64 `json:"duration"` + Page int64 `json:"page"` + Pages int64 `json:"pages"` } func (q *Queries) GetActivity(ctx context.Context, arg GetActivityParams) ([]GetActivityRow, error) { @@ -211,8 +211,8 @@ func (q *Queries) GetActivity(ctx context.Context, arg GetActivityParams) ([]Get &i.Title, &i.Author, &i.Duration, - &i.CurrentPage, - &i.TotalPages, + &i.Page, + &i.Pages, ); err != nil { return nil, err } @@ -238,7 +238,7 @@ WITH RECURSIVE last_30_days AS ( ), activity_records AS ( SELECT - sum(duration) AS seconds_read, + SUM(duration) AS seconds_read, DATE(start_time, time_offset) AS day FROM activity LEFT JOIN users ON users.id = activity.user_id @@ -290,10 +290,10 @@ func (q *Queries) GetDailyReadStats(ctx context.Context, userID string) ([]GetDa const getDatabaseInfo = `-- name: GetDatabaseInfo :one SELECT - (SELECT count(rowid) FROM activity WHERE activity.user_id = ?1) AS activity_size, - (SELECT count(rowid) FROM documents) AS documents_size, - (SELECT count(rowid) FROM document_progress WHERE document_progress.user_id = ?1) AS progress_size, - (SELECT count(rowid) FROM devices WHERE devices.user_id = ?1) AS devices_size + (SELECT COUNT(rowid) FROM activity WHERE activity.user_id = ?1) AS activity_size, + (SELECT COUNT(rowid) FROM documents) AS documents_size, + (SELECT COUNT(rowid) FROM document_progress WHERE document_progress.user_id = ?1) AS progress_size, + (SELECT COUNT(rowid) FROM devices WHERE devices.user_id = ?1) AS devices_size LIMIT 1 ` @@ -451,13 +451,13 @@ func (q *Queries) GetDocument(ctx context.Context, documentID string) (Document, const getDocumentDaysRead = `-- name: GetDocumentDaysRead :one WITH document_days AS ( SELECT DATE(start_time, time_offset) AS dates - FROM rescaled_activity - JOIN users ON users.id = rescaled_activity.user_id + FROM activity + JOIN users ON users.id = activity.user_id WHERE document_id = ?1 AND user_id = ?2 GROUP BY dates ) -SELECT CAST(count(*) AS INTEGER) AS days_read +SELECT CAST(COUNT(*) AS INTEGER) AS days_read FROM document_days ` @@ -475,8 +475,8 @@ func (q *Queries) GetDocumentDaysRead(ctx context.Context, arg GetDocumentDaysRe const getDocumentReadStats = `-- name: GetDocumentReadStats :one SELECT - count(DISTINCT page) AS pages_read, - sum(duration) AS total_time + COUNT(DISTINCT page) AS pages_read, + SUM(duration) AS total_time FROM rescaled_activity WHERE document_id = ?1 AND user_id = ?2 @@ -503,7 +503,7 @@ func (q *Queries) GetDocumentReadStats(ctx context.Context, arg GetDocumentReadS const getDocumentReadStatsCapped = `-- name: GetDocumentReadStatsCapped :one WITH capped_stats AS ( - SELECT min(sum(duration), CAST(?1 AS INTEGER)) AS durations + SELECT MIN(SUM(duration), CAST(?1 AS INTEGER)) AS durations FROM rescaled_activity WHERE document_id = ?2 AND user_id = ?3 @@ -511,8 +511,8 @@ WITH capped_stats AS ( GROUP BY page ) SELECT - CAST(count(*) AS INTEGER) AS pages_read, - CAST(sum(durations) AS INTEGER) AS total_time + CAST(COUNT(*) AS INTEGER) AS pages_read, + CAST(SUM(durations) AS INTEGER) AS total_time FROM capped_stats ` @@ -546,15 +546,15 @@ WITH true_progress AS ( start_time AS last_read, SUM(duration) AS total_time_seconds, document_id, - current_page, - total_pages, + page, + pages, -- Determine Read Pages - COUNT(DISTINCT current_page) AS read_pages, + COUNT(DISTINCT page) AS read_pages, -- Derive Percentage of Book - ROUND(CAST(current_page AS REAL) / CAST(total_pages AS REAL) * 100, 2) AS percentage - FROM activity + ROUND(CAST(page AS REAL) / CAST(pages AS REAL) * 100, 2) AS percentage + FROM rescaled_activity WHERE user_id = ?1 AND document_id = ?2 GROUP BY document_id @@ -564,8 +564,8 @@ WITH true_progress AS ( SELECT documents.id, documents.md5, documents.filepath, documents.coverfile, documents.title, documents.author, documents.series, documents.series_index, documents.lang, documents.description, documents.words, documents.gbid, documents.olid, documents.isbn10, documents.isbn13, documents.synced, documents.deleted, documents.updated_at, documents.created_at, - CAST(IFNULL(current_page, 0) AS INTEGER) AS current_page, - CAST(IFNULL(total_pages, 0) AS INTEGER) AS total_pages, + CAST(IFNULL(page, 0) AS INTEGER) AS page, + CAST(IFNULL(pages, 0) AS INTEGER) AS pages, CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds, CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read, CAST(IFNULL(read_pages, 0) AS INTEGER) AS read_pages, @@ -618,8 +618,8 @@ type GetDocumentWithStatsRow struct { Deleted bool `json:"-"` UpdatedAt time.Time `json:"updated_at"` CreatedAt time.Time `json:"created_at"` - CurrentPage int64 `json:"current_page"` - TotalPages int64 `json:"total_pages"` + Page int64 `json:"page"` + Pages int64 `json:"pages"` TotalTimeSeconds int64 `json:"total_time_seconds"` LastRead string `json:"last_read"` ReadPages int64 `json:"read_pages"` @@ -650,8 +650,8 @@ func (q *Queries) GetDocumentWithStats(ctx context.Context, arg GetDocumentWithS &i.Deleted, &i.UpdatedAt, &i.CreatedAt, - &i.CurrentPage, - &i.TotalPages, + &i.Page, + &i.Pages, &i.TotalTimeSeconds, &i.LastRead, &i.ReadPages, @@ -722,9 +722,9 @@ WITH true_progress AS ( start_time AS last_read, SUM(duration) AS total_time_seconds, document_id, - current_page, - total_pages, - ROUND(CAST(current_page AS REAL) / CAST(total_pages AS REAL) * 100, 2) AS percentage + page, + pages, + ROUND(CAST(page AS REAL) / CAST(pages AS REAL) * 100, 2) AS percentage FROM activity WHERE user_id = ?1 GROUP BY document_id @@ -733,8 +733,8 @@ WITH true_progress AS ( SELECT documents.id, documents.md5, documents.filepath, documents.coverfile, documents.title, documents.author, documents.series, documents.series_index, documents.lang, documents.description, documents.words, documents.gbid, documents.olid, documents.isbn10, documents.isbn13, documents.synced, documents.deleted, documents.updated_at, documents.created_at, - CAST(IFNULL(current_page, 0) AS INTEGER) AS current_page, - CAST(IFNULL(total_pages, 0) AS INTEGER) AS total_pages, + CAST(IFNULL(page, 0) AS INTEGER) AS page, + CAST(IFNULL(pages, 0) AS INTEGER) AS pages, CAST(IFNULL(total_time_seconds, 0) AS INTEGER) AS total_time_seconds, CAST(DATETIME(IFNULL(last_read, "1970-01-01"), time_offset) AS TEXT) AS last_read, @@ -779,8 +779,8 @@ type GetDocumentsWithStatsRow struct { Deleted bool `json:"-"` UpdatedAt time.Time `json:"updated_at"` CreatedAt time.Time `json:"created_at"` - CurrentPage int64 `json:"current_page"` - TotalPages int64 `json:"total_pages"` + Page int64 `json:"page"` + Pages int64 `json:"pages"` TotalTimeSeconds int64 `json:"total_time_seconds"` LastRead string `json:"last_read"` Percentage float64 `json:"percentage"` @@ -815,8 +815,8 @@ func (q *Queries) GetDocumentsWithStats(ctx context.Context, arg GetDocumentsWit &i.Deleted, &i.UpdatedAt, &i.CreatedAt, - &i.CurrentPage, - &i.TotalPages, + &i.Page, + &i.Pages, &i.TotalTimeSeconds, &i.LastRead, &i.Percentage, @@ -1002,7 +1002,7 @@ partitions AS ( ), streaks AS ( SELECT - count(*) AS streak, + COUNT(*) AS streak, MIN(read_window) AS start_date, MAX(read_window) AS end_date, time_offset diff --git a/database/schema.sql b/database/schema.sql index eb04bc5..f55d319 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -110,8 +110,8 @@ CREATE TABLE IF NOT EXISTS activity ( start_time DATETIME NOT NULL, duration INTEGER NOT NULL, - current_page INTEGER NOT NULL, - total_pages INTEGER NOT NULL, + page INTEGER NOT NULL, + pages INTEGER NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id), @@ -119,6 +119,13 @@ CREATE TABLE IF NOT EXISTS activity ( FOREIGN KEY (device_id) REFERENCES devices (id) ); +-- Indexes +CREATE INDEX IF NOT EXISTS activity_start_time ON activity (start_time); +CREATE INDEX IF NOT EXISTS activity_user_id_document_id ON activity ( + user_id, + document_id +); + -- Update Trigger CREATE TRIGGER IF NOT EXISTS update_documents_updated_at BEFORE UPDATE ON documents BEGIN @@ -130,20 +137,21 @@ END; -- Rescaled Activity View (Adapted from KOReader) CREATE VIEW IF NOT EXISTS rescaled_activity AS -WITH RECURSIVE numbers (idx) AS ( +WITH RECURSIVE nums (idx) AS ( SELECT 1 AS idx UNION ALL SELECT idx + 1 - FROM numbers + FROM nums LIMIT 1000 ), -total_pages AS ( +current_pages AS ( SELECT document_id, - total_pages AS pages + user_id, + pages FROM activity - GROUP BY document_id + GROUP BY document_id, user_id HAVING MAX(start_time) ORDER BY start_time DESC ), @@ -153,25 +161,50 @@ intermediate AS ( activity.document_id, activity.device_id, activity.user_id, - activity.current_page, - activity.total_pages, - total_pages.pages, activity.start_time, activity.duration, - numbers.idx, - -- Derive First Page - ((activity.current_page - 1) * total_pages.pages) / activity.total_pages + activity.page, + current_pages.pages, + + -- Derive first page + ((activity.page - 1) * current_pages.pages) / activity.pages + 1 AS first_page, - -- Derive Last Page + + -- Derive last page MAX( - ((activity.current_page - 1) * total_pages.pages) - / activity.total_pages + ((activity.page - 1) * current_pages.pages) + / activity.pages + 1, - (activity.current_page * total_pages.pages) / activity.total_pages + (activity.page * current_pages.pages) / activity.pages ) AS last_page + FROM activity - INNER JOIN total_pages ON total_pages.document_id = activity.document_id - INNER JOIN numbers ON numbers.idx <= (last_page - first_page + 1) + INNER JOIN current_pages ON + current_pages.document_id = activity.document_id + AND current_pages.user_id = activity.user_id +), + +-- Improves performance +num_limit AS ( + SELECT * FROM nums + LIMIT (SELECT MAX(last_page - first_page + 1) FROM intermediate) +), + +rescaled_raw AS ( + SELECT + document_id, + device_id, + user_id, + start_time, + last_page, + pages, + first_page + num_limit.idx - 1 AS page, + duration / ( + last_page - first_page + 1.0 + ) AS duration + FROM intermediate + JOIN num_limit ON + num_limit.idx <= (last_page - first_page + 1) ) SELECT @@ -179,6 +212,13 @@ SELECT device_id, user_id, start_time, - first_page + idx - 1 AS page, - duration / (last_page - first_page + 1) AS duration -FROM intermediate; + pages, + page, + + -- Round up if last page (maintains total duration) + CAST(CASE + WHEN page = last_page AND duration != CAST(duration AS INTEGER) + THEN duration + 1 + ELSE duration + END AS INTEGER) AS duration +FROM rescaled_raw; diff --git a/templates/activity.html b/templates/activity.html index 49d1a1c..bf33603 100644 --- a/templates/activity.html +++ b/templates/activity.html @@ -45,7 +45,7 @@

{{ $activity.Duration }}

-

{{ $activity.CurrentPage }} / {{ $activity.TotalPages }}

+

{{ $activity.Page }} / {{ $activity.Pages }}

{{end}} diff --git a/templates/document-edit.html b/templates/document-edit.html index 68d9027..2075959 100644 --- a/templates/document-edit.html +++ b/templates/document-edit.html @@ -137,7 +137,7 @@

Progress

- {{ .Data.CurrentPage }} / {{ .Data.TotalPages }} ({{ .Data.Percentage }}%) + {{ .Data.Page }} / {{ .Data.Pages }} ({{ .Data.Percentage }}%)

diff --git a/templates/document.html b/templates/document.html index 76bb601..cf63497 100644 --- a/templates/document.html +++ b/templates/document.html @@ -344,7 +344,7 @@

Progress

- {{ .Data.CurrentPage }} / {{ .Data.TotalPages }} ({{ .Data.Percentage }}%) + {{ .Data.Page }} / {{ .Data.Pages }} ({{ .Data.Percentage }}%)