Compare commits
No commits in common. "71898c39e73b6d3017ba077f32d29cf71b229314" and "425f469097a1891e5e6ba559703b6fed410190ad" have entirely different histories.
71898c39e7
...
425f469097
@ -169,13 +169,6 @@ func (api *API) setProgress(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Statistic
|
|
||||||
log.Info("[setProgress] UpdateDocumentUserStatistic Running...")
|
|
||||||
if err := api.DB.UpdateDocumentUserStatistic(rPosition.DocumentID, rUser.(string)); err != nil {
|
|
||||||
log.Error("[setProgress] UpdateDocumentUserStatistic Error:", err)
|
|
||||||
}
|
|
||||||
log.Info("[setProgress] UpdateDocumentUserStatistic Complete")
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"document": progress.DocumentID,
|
"document": progress.DocumentID,
|
||||||
"timestamp": progress.CreatedAt,
|
"timestamp": progress.CreatedAt,
|
||||||
@ -292,15 +285,6 @@ func (api *API) addActivities(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Statistic
|
|
||||||
for _, doc := range allDocuments {
|
|
||||||
log.Info("[addActivities] UpdateDocumentUserStatistic Running...")
|
|
||||||
if err := api.DB.UpdateDocumentUserStatistic(doc, rUser.(string)); err != nil {
|
|
||||||
log.Error("[addActivities] UpdateDocumentUserStatistic Error:", err)
|
|
||||||
}
|
|
||||||
log.Info("[addActivities] UpdateDocumentUserStatistic Complete")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"added": len(rActivity.Activity),
|
"added": len(rActivity.Activity),
|
||||||
})
|
})
|
||||||
|
@ -73,6 +73,7 @@ function populateMetadata(data) {
|
|||||||
**/
|
**/
|
||||||
class EBookReader {
|
class EBookReader {
|
||||||
bookState = {
|
bookState = {
|
||||||
|
currentWord: 0,
|
||||||
pages: 0,
|
pages: 0,
|
||||||
percentage: 0,
|
percentage: 0,
|
||||||
progress: "",
|
progress: "",
|
||||||
@ -114,18 +115,22 @@ class EBookReader {
|
|||||||
* Load progress and generate locations
|
* Load progress and generate locations
|
||||||
**/
|
**/
|
||||||
async setupReader() {
|
async setupReader() {
|
||||||
// Get Word Count
|
// Get Word Count (If Needed)
|
||||||
this.bookState.words = await this.countWords();
|
if (this.bookState.words == 0)
|
||||||
|
this.bookState.words = await this.countWords();
|
||||||
|
|
||||||
// Load Progress
|
// Load Progress
|
||||||
let { cfi } = await this.getCFIFromXPath(this.bookState.progress);
|
let { cfi } = await this.getCFIFromXPath(this.bookState.progress);
|
||||||
|
this.bookState.currentWord = cfi
|
||||||
|
? this.bookState.percentage * (this.bookState.words / 100)
|
||||||
|
: 0;
|
||||||
|
|
||||||
let getStats = async function () {
|
let getStats = function () {
|
||||||
// Start Timer
|
// Start Timer
|
||||||
this.bookState.pageStart = Date.now();
|
this.bookState.pageStart = Date.now();
|
||||||
|
|
||||||
// Get Stats
|
// Get Stats
|
||||||
let stats = await this.getBookStats();
|
let stats = this.getBookStats();
|
||||||
this.updateBookStatElements(stats);
|
this.updateBookStatElements(stats);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
@ -655,7 +660,7 @@ class EBookReader {
|
|||||||
this.bookState.pageStart = Date.now();
|
this.bookState.pageStart = Date.now();
|
||||||
|
|
||||||
// Update Stats
|
// Update Stats
|
||||||
let stats = await this.getBookStats();
|
let stats = this.getBookStats();
|
||||||
this.updateBookStatElements(stats);
|
this.updateBookStatElements(stats);
|
||||||
|
|
||||||
// Create Progress
|
// Create Progress
|
||||||
@ -669,11 +674,16 @@ class EBookReader {
|
|||||||
// Render Previous Page
|
// Render Previous Page
|
||||||
await this.rendition.prev();
|
await this.rendition.prev();
|
||||||
|
|
||||||
|
// Update Current Word
|
||||||
|
let pageWords = await this.getVisibleWordCount();
|
||||||
|
this.bookState.currentWord -= pageWords;
|
||||||
|
if (this.bookState.currentWord < 0) this.bookState.currentWord = 0;
|
||||||
|
|
||||||
// Reset Read Timer
|
// Reset Read Timer
|
||||||
this.bookState.pageStart = Date.now();
|
this.bookState.pageStart = Date.now();
|
||||||
|
|
||||||
// Update Stats
|
// Update Stats
|
||||||
let stats = await this.getBookStats();
|
let stats = this.getBookStats();
|
||||||
this.updateBookStatElements(stats);
|
this.updateBookStatElements(stats);
|
||||||
|
|
||||||
// Create Progress
|
// Create Progress
|
||||||
@ -709,8 +719,9 @@ class EBookReader {
|
|||||||
|
|
||||||
// Update Current Word
|
// Update Current Word
|
||||||
let pageWords = await this.getVisibleWordCount();
|
let pageWords = await this.getVisibleWordCount();
|
||||||
let currentWord = await this.getBookWordPosition();
|
let startingWord = this.bookState.currentWord;
|
||||||
let percentRead = pageWords / this.bookState.words;
|
let percentRead = pageWords / this.bookState.words;
|
||||||
|
this.bookState.currentWord += pageWords;
|
||||||
|
|
||||||
let pageWPM = pageWords / (elapsedTime / 60000);
|
let pageWPM = pageWords / (elapsedTime / 60000);
|
||||||
console.log("[createActivity] Page WPM:", pageWPM);
|
console.log("[createActivity] Page WPM:", pageWPM);
|
||||||
@ -729,10 +740,10 @@ class EBookReader {
|
|||||||
|
|
||||||
// Exclude 0 Pages
|
// Exclude 0 Pages
|
||||||
if (totalPages == 0)
|
if (totalPages == 0)
|
||||||
return console.warn("[createActivity] Invalid Total Pages (0)");
|
return console.log("[createActivity] Invalid Total Pages (0)");
|
||||||
|
|
||||||
let currentPage = Math.round(
|
let currentPage = Math.round(
|
||||||
(currentWord * totalPages) / this.bookState.words
|
(startingWord * totalPages) / this.bookState.words
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create Activity Event
|
// Create Activity Event
|
||||||
@ -794,8 +805,6 @@ class EBookReader {
|
|||||||
// Update Pointers
|
// Update Pointers
|
||||||
let currentCFI = await this.rendition.currentLocation();
|
let currentCFI = await this.rendition.currentLocation();
|
||||||
let { element, xpath } = await this.getXPathFromCFI(currentCFI.start.cfi);
|
let { element, xpath } = await this.getXPathFromCFI(currentCFI.start.cfi);
|
||||||
let currentWord = await this.getBookWordPosition();
|
|
||||||
console.log("[createProgress] Current Word:", currentWord);
|
|
||||||
this.bookState.progress = xpath;
|
this.bookState.progress = xpath;
|
||||||
this.bookState.progressElement = element;
|
this.bookState.progressElement = element;
|
||||||
|
|
||||||
@ -805,7 +814,9 @@ class EBookReader {
|
|||||||
device_id: this.readerSettings.deviceID,
|
device_id: this.readerSettings.deviceID,
|
||||||
device: this.readerSettings.deviceName,
|
device: this.readerSettings.deviceName,
|
||||||
percentage:
|
percentage:
|
||||||
Math.round((currentWord / this.bookState.words) * 100000) / 100000,
|
Math.round(
|
||||||
|
(this.bookState.currentWord / this.bookState.words) * 100000
|
||||||
|
) / 100000,
|
||||||
progress: this.bookState.progress,
|
progress: this.bookState.progress,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -874,13 +885,12 @@ class EBookReader {
|
|||||||
/**
|
/**
|
||||||
* Get chapter pages, name and progress percentage
|
* Get chapter pages, name and progress percentage
|
||||||
**/
|
**/
|
||||||
async getBookStats() {
|
getBookStats() {
|
||||||
let currentProgress = this.sectionProgress();
|
let currentProgress = this.sectionProgress();
|
||||||
if (!currentProgress) return;
|
if (!currentProgress) return;
|
||||||
let { sectionPages, sectionCurrentPage } = currentProgress;
|
let { sectionPages, sectionCurrentPage } = currentProgress;
|
||||||
|
|
||||||
let currentLocation = this.rendition.currentLocation();
|
let currentLocation = this.rendition.currentLocation();
|
||||||
let currentWord = await this.getBookWordPosition();
|
|
||||||
|
|
||||||
let currentTOC = this.book.navigation.toc.find(
|
let currentTOC = this.book.navigation.toc.find(
|
||||||
(item) => item.href == currentLocation.start.href
|
(item) => item.href == currentLocation.start.href
|
||||||
@ -891,7 +901,9 @@ class EBookReader {
|
|||||||
sectionTotalPages: sectionPages,
|
sectionTotalPages: sectionPages,
|
||||||
chapterName: currentTOC ? currentTOC.label.trim() : "N/A",
|
chapterName: currentTOC ? currentTOC.label.trim() : "N/A",
|
||||||
percentage:
|
percentage:
|
||||||
Math.round((currentWord / this.bookState.words) * 10000) / 100,
|
Math.round(
|
||||||
|
(this.bookState.currentWord / this.bookState.words) * 10000
|
||||||
|
) / 100,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1064,43 +1076,6 @@ class EBookReader {
|
|||||||
* Get visible word count - used for reading stats
|
* Get visible word count - used for reading stats
|
||||||
**/
|
**/
|
||||||
async getVisibleWordCount() {
|
async getVisibleWordCount() {
|
||||||
let visibleText = await this.getVisibleText();
|
|
||||||
return visibleText.trim().split(/\s+/).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the word number of the whole book for the first visible word.
|
|
||||||
**/
|
|
||||||
async getBookWordPosition() {
|
|
||||||
// Get Contents & Spine
|
|
||||||
let contents = this.rendition.getContents()[0];
|
|
||||||
let spineItem = this.book.spine.get(contents.sectionIndex);
|
|
||||||
|
|
||||||
// Get CFI Range
|
|
||||||
let firstCFI = spineItem.cfiFromElement(
|
|
||||||
spineItem.document.body.children[0]
|
|
||||||
);
|
|
||||||
let currentLocation = await this.rendition.currentLocation();
|
|
||||||
let cfiRange = this.getCFIRange(firstCFI, currentLocation.start.cfi);
|
|
||||||
|
|
||||||
// Get Chapter Text (Before Current Position)
|
|
||||||
let textRange = await this.book.getRange(cfiRange);
|
|
||||||
let chapterText = textRange.toString();
|
|
||||||
|
|
||||||
// Get Chapter & Book Positions
|
|
||||||
let chapterWordPosition = chapterText.trim().split(/\s+/).length;
|
|
||||||
let preChapterWordPosition = this.book.spine.spineItems
|
|
||||||
.slice(0, contents.sectionIndex)
|
|
||||||
.reduce((totalCount, item) => totalCount + item.wordCount, 0);
|
|
||||||
|
|
||||||
// Return Current Word Pointer
|
|
||||||
return chapterWordPosition + preChapterWordPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get visible text - used for word counts
|
|
||||||
**/
|
|
||||||
async getVisibleText() {
|
|
||||||
// Force Expand & Resize (Race Condition Issue)
|
// Force Expand & Resize (Race Condition Issue)
|
||||||
this.rendition.manager.visible().forEach((item) => item.expand());
|
this.rendition.manager.visible().forEach((item) => item.expand());
|
||||||
|
|
||||||
@ -1117,7 +1092,7 @@ class EBookReader {
|
|||||||
let visibleText = textRange.toString();
|
let visibleText = textRange.toString();
|
||||||
|
|
||||||
// Split on Whitespace
|
// Split on Whitespace
|
||||||
return visibleText;
|
return visibleText.trim().split(/\s+/).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1172,17 +1147,14 @@ class EBookReader {
|
|||||||
* of progress percentage. Implementation returns the same number as the
|
* of progress percentage. Implementation returns the same number as the
|
||||||
* server side implementation.
|
* server side implementation.
|
||||||
**/
|
**/
|
||||||
async countWords() {
|
countWords() {
|
||||||
let spineWC = await Promise.all(
|
// Iterate over each item in the spine, render, and count words.
|
||||||
this.book.spine.spineItems.map(async (item) => {
|
return this.book.spine.spineItems.reduce(async (totalCount, item) => {
|
||||||
let newDoc = await item.load(this.book.load.bind(this.book));
|
let currentCount = await totalCount;
|
||||||
let spineWords = newDoc.innerText.trim().split(/\s+/).length;
|
let newDoc = await item.load(this.book.load.bind(this.book));
|
||||||
item.wordCount = spineWords;
|
let itemCount = newDoc.innerText.trim().split(/\s+/).length;
|
||||||
return spineWords;
|
return currentCount + itemCount;
|
||||||
})
|
}, 0);
|
||||||
);
|
|
||||||
|
|
||||||
return spineWC.reduce((totalCount, itemCount) => totalCount + itemCount, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,9 +23,6 @@ var ddl string
|
|||||||
//go:embed update_temp_tables.sql
|
//go:embed update_temp_tables.sql
|
||||||
var tsql string
|
var tsql string
|
||||||
|
|
||||||
//go:embed update_document_user_statistics.sql
|
|
||||||
var doc_user_stat_sql string
|
|
||||||
|
|
||||||
func NewMgr(c *config.Config) *DBManager {
|
func NewMgr(c *config.Config) *DBManager {
|
||||||
// Create Manager
|
// Create Manager
|
||||||
dbm := &DBManager{
|
dbm := &DBManager{
|
||||||
@ -63,21 +60,6 @@ func (dbm *DBManager) Shutdown() error {
|
|||||||
return dbm.DB.Close()
|
return dbm.DB.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbm *DBManager) UpdateDocumentUserStatistic(documentID string, userID string) error {
|
|
||||||
// Prepare Statement
|
|
||||||
stmt, err := dbm.DB.PrepareContext(dbm.Ctx, doc_user_stat_sql)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
if _, err := stmt.ExecContext(dbm.Ctx, documentID, userID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbm *DBManager) CacheTempTables() error {
|
func (dbm *DBManager) CacheTempTables() error {
|
||||||
if _, err := dbm.DB.ExecContext(dbm.Ctx, tsql); err != nil {
|
if _, err := dbm.DB.ExecContext(dbm.Ctx, tsql); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -136,9 +136,7 @@ CREATE TEMPORARY TABLE IF NOT EXISTS document_user_statistics (
|
|||||||
read_percentage REAL NOT NULL,
|
read_percentage REAL NOT NULL,
|
||||||
percentage REAL NOT NULL,
|
percentage REAL NOT NULL,
|
||||||
words_read INTEGER NOT NULL,
|
words_read INTEGER NOT NULL,
|
||||||
wpm REAL NOT NULL,
|
wpm REAL NOT NULL
|
||||||
|
|
||||||
UNIQUE(document_id, user_id) ON CONFLICT REPLACE
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@ -327,8 +325,6 @@ current_progress AS (
|
|||||||
WHERE
|
WHERE
|
||||||
dp.user_id = iga.user_id
|
dp.user_id = iga.user_id
|
||||||
AND dp.document_id = iga.document_id
|
AND dp.document_id = iga.document_id
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
), end_percentage) AS percentage
|
), end_percentage) AS percentage
|
||||||
FROM intermediate_ga AS iga
|
FROM intermediate_ga AS iga
|
||||||
GROUP BY user_id, document_id
|
GROUP BY user_id, document_id
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
INSERT INTO document_user_statistics
|
|
||||||
WITH intermediate_ga AS (
|
|
||||||
SELECT
|
|
||||||
ga1.id AS row_id,
|
|
||||||
ga1.user_id,
|
|
||||||
ga1.document_id,
|
|
||||||
ga1.duration,
|
|
||||||
ga1.start_time,
|
|
||||||
ga1.start_percentage,
|
|
||||||
ga1.end_percentage,
|
|
||||||
|
|
||||||
-- Find Overlapping Events (Assign Unique ID)
|
|
||||||
(
|
|
||||||
SELECT MIN(id)
|
|
||||||
FROM activity AS ga2
|
|
||||||
WHERE
|
|
||||||
ga1.document_id = ga2.document_id
|
|
||||||
AND ga1.user_id = ga2.user_id
|
|
||||||
AND ga1.start_percentage <= ga2.end_percentage
|
|
||||||
AND ga1.end_percentage >= ga2.start_percentage
|
|
||||||
) AS group_leader
|
|
||||||
FROM activity AS ga1
|
|
||||||
WHERE
|
|
||||||
document_id = ?
|
|
||||||
AND user_id = ?
|
|
||||||
),
|
|
||||||
grouped_activity AS (
|
|
||||||
SELECT
|
|
||||||
user_id,
|
|
||||||
document_id,
|
|
||||||
MAX(start_time) AS start_time,
|
|
||||||
MIN(start_percentage) AS start_percentage,
|
|
||||||
MAX(end_percentage) AS end_percentage,
|
|
||||||
MAX(end_percentage) - MIN(start_percentage) AS read_percentage,
|
|
||||||
SUM(duration) AS duration
|
|
||||||
FROM intermediate_ga
|
|
||||||
GROUP BY group_leader
|
|
||||||
),
|
|
||||||
current_progress AS (
|
|
||||||
SELECT
|
|
||||||
user_id,
|
|
||||||
document_id,
|
|
||||||
COALESCE((
|
|
||||||
SELECT percentage
|
|
||||||
FROM document_progress AS dp
|
|
||||||
WHERE
|
|
||||||
dp.user_id = iga.user_id
|
|
||||||
AND dp.document_id = iga.document_id
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
), end_percentage) AS percentage
|
|
||||||
FROM intermediate_ga AS iga
|
|
||||||
GROUP BY user_id, document_id
|
|
||||||
HAVING MAX(start_time)
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
ga.document_id,
|
|
||||||
ga.user_id,
|
|
||||||
MAX(start_time) AS last_read,
|
|
||||||
SUM(duration) AS total_time_seconds,
|
|
||||||
SUM(read_percentage) AS read_percentage,
|
|
||||||
cp.percentage,
|
|
||||||
|
|
||||||
(CAST(COALESCE(d.words, 0.0) AS REAL) * SUM(read_percentage))
|
|
||||||
AS words_read,
|
|
||||||
|
|
||||||
(CAST(COALESCE(d.words, 0.0) AS REAL) * SUM(read_percentage))
|
|
||||||
/ (SUM(duration) / 60.0) AS wpm
|
|
||||||
FROM grouped_activity AS ga
|
|
||||||
INNER JOIN
|
|
||||||
current_progress AS cp
|
|
||||||
ON ga.user_id = cp.user_id AND ga.document_id = cp.document_id
|
|
||||||
INNER JOIN
|
|
||||||
documents AS d
|
|
||||||
ON d.id = ga.document_id
|
|
||||||
GROUP BY ga.document_id, ga.user_id
|
|
||||||
ORDER BY wpm DESC;
|
|
Loading…
Reference in New Issue
Block a user