From 842105bd978c16e1cb11f21b504f0c8e28fb3fd3 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 18 May 2024 14:33:45 -0400 Subject: [PATCH] fix: modify updateProgress function to handle timeout issues (#334) --- .../TestThemerrController.cs | 10 +- .../Api/ThemerrController.cs | 23 +- .../Configuration/configPage.html | 342 +++++++++++------- Locale/en.json | 2 + 4 files changed, 223 insertions(+), 154 deletions(-) diff --git a/Jellyfin.Plugin.Themerr.Tests/TestThemerrController.cs b/Jellyfin.Plugin.Themerr.Tests/TestThemerrController.cs index 1d305cd..ac19687 100644 --- a/Jellyfin.Plugin.Themerr.Tests/TestThemerrController.cs +++ b/Jellyfin.Plugin.Themerr.Tests/TestThemerrController.cs @@ -58,18 +58,18 @@ public void TestGetProgress() var result = _controller.GetProgress(); Assert.IsType(result); - // ensure result["media_count"] is an int + // ensure the following properties are int Assert.IsType(((JsonResult)result).Value?.GetType().GetProperty("media_count")?.GetValue(((JsonResult)result).Value, null)); - - // ensure result["media_percent_complete"] is an int - Assert.IsType(((JsonResult)result).Value?.GetType().GetProperty("media_percent_complete")?.GetValue(((JsonResult)result).Value, null)); + Assert.IsType(((JsonResult)result).Value?.GetType().GetProperty("media_with_themes")?.GetValue(((JsonResult)result).Value, null)); + Assert.IsType(((JsonResult)result).Value?.GetType().GetProperty("total_pages")?.GetValue(((JsonResult)result).Value, null)); // ensure result["items"] is an array list Assert.IsType(((JsonResult)result).Value?.GetType().GetProperty("items")?.GetValue(((JsonResult)result).Value, null)); // ensure int values are 0 Assert.Equal(0, ((JsonResult)result).Value?.GetType().GetProperty("media_count")?.GetValue(((JsonResult)result).Value, null)); - Assert.Equal(0, ((JsonResult)result).Value?.GetType().GetProperty("media_percent_complete")?.GetValue(((JsonResult)result).Value, null)); + Assert.Equal(0, ((JsonResult)result).Value?.GetType().GetProperty("media_with_themes")?.GetValue(((JsonResult)result).Value, null)); + Assert.Equal(0, ((JsonResult)result).Value?.GetType().GetProperty("total_pages")?.GetValue(((JsonResult)result).Value, null)); // ensure array list has no items Assert.Equal(0, (((JsonResult)result).Value?.GetType().GetProperty("items")?.GetValue(((JsonResult)result).Value, null) as ArrayList)?.Count); diff --git a/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs b/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs index b698ab7..519a870 100644 --- a/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs +++ b/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs @@ -84,23 +84,30 @@ public async Task TriggerUpdateRequest() /// "media_percent_complete": ThemedItems.Count / BaseItems.Count * 100, /// } /// + /// The page number to return. + /// The number of items to return per page. /// JSON object containing progress data. [HttpGet("GetProgress")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetProgress() + public ActionResult GetProgress(int page = 1, int pageSize = 10) { var tmpItems = new ArrayList(); var mediaCount = 0; var mediaWithThemes = 0; - var mediaPercentComplete = 0; var items = _themerrManager.GetTmdbItemsFromLibrary(); // sort items by name, then year - var enumerable = items.OrderBy(i => i.Name).ThenBy(i => i.ProductionYear); + var enumerable = items.OrderBy(i => i.Name).ThenBy(i => i.ProductionYear).ToList(); - foreach (var item in enumerable) + // calculate total media count before applying pagination + var totalMediaCount = enumerable.Count; + + // apply pagination + var pagedItems = enumerable.Skip((page - 1) * pageSize).Take(pageSize); + + foreach (var item in pagedItems) { var year = item.ProductionYear; var issueUrl = _themerrManager.GetIssueUrl(item); @@ -125,16 +132,12 @@ public ActionResult GetProgress() } } - if (mediaCount > 0) - { - mediaPercentComplete = (int)Math.Round((double)mediaWithThemes / mediaCount * 100); - } - var tmpObject = new { items = tmpItems, media_count = mediaCount, - media_percent_complete = mediaPercentComplete, + media_with_themes = mediaWithThemes, + total_pages = (int)Math.Ceiling((double)totalMediaCount / pageSize), }; _logger.LogInformation("Progress Items: {Items}", JsonConvert.SerializeObject(tmpObject)); diff --git a/Jellyfin.Plugin.Themerr/Configuration/configPage.html b/Jellyfin.Plugin.Themerr/Configuration/configPage.html index bce43bc..d1c409b 100644 --- a/Jellyfin.Plugin.Themerr/Configuration/configPage.html +++ b/Jellyfin.Plugin.Themerr/Configuration/configPage.html @@ -66,7 +66,18 @@

Themerr


-
+
+

Dashboard Loading...

+
+
+ 0% +
+
+
+ +
@@ -191,150 +202,203 @@

Themerr

} } - function updateProgress() { - let request = { - url: _ApiClient.getUrl('/Themerr/GetProgress'), - type: 'GET', - dataType: 'json' // this should automatically return response.json() - } + async function updateProgress() { + let all_items = [] + let page = 1 + let pageSize = 10 + let total_media_count = 0 + let total_media_with_themes = 0 + let total_pages = 1 - _ApiClient.fetch(request).then(function (response) { - console.log(LogPrefix + "media_count: " + response["media_count"]) - console.log(LogPrefix + "media_percent_complete: " + response["media_percent_complete"]) + for (; page <= total_pages; page++) { + let request = { + url: _ApiClient.getUrl( + '/Themerr/GetProgress', + { + page: page, + pageSize: pageSize + }, + ), + type: 'GET', + dataType: 'json' // this should automatically return response.json() + }; - if (response["media_count"] === 0) { - let message = "No media found!" - console.log(LogPrefix + message) - return - } + try { + let response = await _ApiClient.fetch(request); - // get dashboard container - let ThemerrDashboard = document.querySelector('#ThemerrDashboard') - - // create progress bar - let progressBar = document.createElement('div') - progressBar.setAttribute('is', 'emby-progressbar') - progressBar.setAttribute('class', 'itemProgressBar') - progressBar.setAttribute('style', 'height: 20px;') - ThemerrDashboard.appendChild(progressBar) - - // progress bar foreground - let progressBarForeground = document.createElement('div') - progressBarForeground.setAttribute('class', 'itemProgressBarForeground') - progressBarForeground.setAttribute('style', 'width: ' + response["media_percent_complete"] + '%;') - progressBar.appendChild(progressBarForeground) - - // progress bar text - let progressBarText = document.createElement('span') - progressBarText.setAttribute('style', 'margin-left: 0.5em;') - progressBarText.innerText = response["media_percent_complete"] + '%' - progressBarForeground.appendChild(progressBarText) - - // add break - ThemerrDashboard.appendChild(document.createElement('br')) - - // create table - let table = document.createElement('table') - table.setAttribute('id', 'ThemerrProgressTable') - table.setAttribute('class', 'detailTable') - ThemerrDashboard.appendChild(table) - - // create table header - let tableHeader = document.createElement('thead') - table.appendChild(tableHeader) - - // create table header row - let tableHeaderRow = document.createElement('tr') - tableHeader.appendChild(tableHeaderRow) - - let columns = ['Title', 'Year', 'Type', 'Contribute', 'Status'] - - // create table header columns - for (let column in columns) { - let tableHeaderColumn = document.createElement('th') - tableHeaderColumn.setAttribute('class', 'detailTableHeaderCell') - tableHeaderColumn.setAttribute('scope', 'col') - tableHeaderColumn.innerText = translate(columns[column].toLowerCase()) - tableHeaderRow.appendChild(tableHeaderColumn) - } + console.log(LogPrefix + "processing page " + page + " of " + response["total_pages"]); + console.log(LogPrefix + "media_count: " + response["media_count"]); + console.log(LogPrefix + "media_with_themes: " + response["media_with_themes"]); - // loop over items - for (let item in response["items"]) { - console.log(LogPrefix + "------------------") - console.log(LogPrefix + "item: " + item) - console.log(LogPrefix + "name: " + response["items"][item]["name"]) - console.log(LogPrefix + "id: " + response["items"][item]["id"]) - console.log(LogPrefix + "issue_url: " + response["items"][item]["issue_url"]) - console.log(LogPrefix + "type: " + response["items"][item]["type"]) - console.log(LogPrefix + "theme_provider: " + response["items"][item]["theme_provider"]) - console.log(LogPrefix + "year: " + response["items"][item]["year"]) - - // create table row - let tableRow = document.createElement('tr') - table.appendChild(tableRow) - - // create table columns - let tableColumnTitle = document.createElement('td') - tableColumnTitle.setAttribute('class', 'detailTableBodyCell') - tableColumnTitle.innerText = response["items"][item]["name"] - tableRow.appendChild(tableColumnTitle) - - let tableColumnYear = document.createElement('td') - tableColumnYear.setAttribute('class', 'detailTableBodyCell') - tableColumnYear.innerText = response["items"][item]["year"] - tableRow.appendChild(tableColumnYear) - - let tableColumnType = document.createElement('td') - tableColumnType.setAttribute('class', 'detailTableBodyCell') - tableColumnType.innerText = translate(response["items"][item]["type"].toLowerCase()) - tableRow.appendChild(tableColumnType) - - let contributeButton = document.createElement('a') - contributeButton.setAttribute('is', 'emby-linkbutton') - contributeButton.setAttribute('class', 'raised headerHelpButton button-submit emby-button') - contributeButton.setAttribute('target', '_blank') - contributeButton.setAttribute('href', response["items"][item]["issue_url"]) - // text added when status is set - - let tableColumnContribute = document.createElement('td') - tableColumnContribute.appendChild(contributeButton) - tableRow.appendChild(tableColumnContribute) - - let tableColumnStatus = document.createElement('td') - tableColumnStatus.setAttribute('class', 'detailTableBodyCell') - - let statusSpan = document.createElement('span') - let statusText = document.createElement('span') - statusSpan.setAttribute('class', 'material-icons') - statusText.setAttribute('class', 'detailTableBodyCell') - if (response["items"][item]["theme_provider"] === 'themerr') { - contributeButton.innerText = translate('edit_button') - statusSpan.classList.add('check') - statusSpan.setAttribute('style', 'margin-right: 6px; color: #00ff00;') - statusText.innerText = translate('themerr_provided') - } else if (response["items"][item]["theme_provider"] === 'user') { - contributeButton.innerText = translate('contribute_button') - statusSpan.classList.add('person') - statusSpan.setAttribute('style', 'margin-right: 6px; color: #00a4dc;') - statusText.innerText = translate('user_provided') - } else { - contributeButton.innerText = translate('add_button') - statusSpan.classList.add('cancel') - statusSpan.setAttribute('style', 'margin-right: 6px; color: #ff0000;') - statusText.innerText = translate('no_theme_song') + if (response["media_count"] === 0 && page === 1) { + let message = "No media found!"; + console.log(LogPrefix + message); + return; } - tableColumnStatus.appendChild(statusSpan) - tableColumnStatus.appendChild(statusText) - tableRow.appendChild(tableColumnStatus) + + total_media_count += response["media_count"]; + total_media_with_themes += response["media_with_themes"]; + all_items = all_items.concat(response["items"]); + + total_pages = response["total_pages"]; // Update totalPages + } catch (error) { + let message = translate('unexpected_error_occured_creating_dashboard'); + Dashboard.alert({ + message: message + }); + console.log(LogPrefix + message); + console.log(LogPrefix + error); } - }).catch(function (error) { - let message = translate('unexpected_error_occured_creating_dashboard') - Dashboard.alert({ - message: message - }) - console.log(LogPrefix + message) - console.log(LogPrefix + error) - }) + + // Calculate the loading progress percentage + let progressPercentage = Math.round((page / total_pages) * 100); + + // Update the loading progress bar + let progressBar = document.querySelector('#ThemerrDashboardLoading .itemProgressBarForeground'); + progressBar.style.width = progressPercentage + '%'; + progressBar.querySelector('span').innerText = progressPercentage + '%'; + } + + // add an arbitrary delay + await new Promise(r => setTimeout(r, 1000)); + + // hide the loading progress bar + let progressBar = document.querySelector('#ThemerrDashboardLoading'); + progressBar.style.display = 'none'; + + // create the dashboard + createDashboard(total_media_count, total_media_with_themes, all_items); + } + + function createDashboard(total_media_count, total_media_with_themes, all_items) { + // get dashboard container + let ThemerrDashboard = document.querySelector('#ThemerrDashboard') + + // show the dashboard + ThemerrDashboard.style.display = 'block' + + // create progress bar + let progressBar = document.createElement('div') + progressBar.setAttribute('is', 'emby-progressbar') + progressBar.setAttribute('class', 'itemProgressBar') + progressBar.setAttribute('style', 'height: 20px;') + ThemerrDashboard.appendChild(progressBar) + + // calculate percent complete + let percentComplete = 0 + if (total_media_count > 0) { + percentComplete = Math.round((total_media_with_themes / total_media_count) * 100) + } + + // progress bar foreground + let progressBarForeground = document.createElement('div') + progressBarForeground.setAttribute('class', 'itemProgressBarForeground') + progressBarForeground.setAttribute('style', 'width: ' + percentComplete + '%;') + progressBar.appendChild(progressBarForeground) + + // progress bar text + let progressBarText = document.createElement('span') + progressBarText.setAttribute('style', 'margin-left: 0.5em;') + progressBarText.innerText = percentComplete + '%' + progressBarForeground.appendChild(progressBarText) + + // add break + ThemerrDashboard.appendChild(document.createElement('br')) + + // create table + let table = document.createElement('table') + table.setAttribute('id', 'ThemerrProgressTable') + table.setAttribute('class', 'detailTable') + ThemerrDashboard.appendChild(table) + + // create table header + let tableHeader = document.createElement('thead') + table.appendChild(tableHeader) + + // create table header row + let tableHeaderRow = document.createElement('tr') + tableHeader.appendChild(tableHeaderRow) + + let columns = ['Title', 'Year', 'Type', 'Contribute', 'Status'] + + // create table header columns + for (let column in columns) { + let tableHeaderColumn = document.createElement('th') + tableHeaderColumn.setAttribute('class', 'detailTableHeaderCell') + tableHeaderColumn.setAttribute('scope', 'col') + tableHeaderColumn.innerText = translate(columns[column].toLowerCase()) + tableHeaderRow.appendChild(tableHeaderColumn) + } + + // loop over items + for (let item in all_items) { + console.log(LogPrefix + "------------------") + console.log(LogPrefix + "item: " + item) + console.log(LogPrefix + "name: " + all_items[item]["name"]) + console.log(LogPrefix + "id: " + all_items[item]["id"]) + console.log(LogPrefix + "issue_url: " + all_items[item]["issue_url"]) + console.log(LogPrefix + "type: " + all_items[item]["type"]) + console.log(LogPrefix + "theme_provider: " + all_items[item]["theme_provider"]) + console.log(LogPrefix + "year: " + all_items[item]["year"]) + + // create table row + let tableRow = document.createElement('tr') + table.appendChild(tableRow) + + // create table columns + let tableColumnTitle = document.createElement('td') + tableColumnTitle.setAttribute('class', 'detailTableBodyCell') + tableColumnTitle.innerText = all_items[item]["name"] + tableRow.appendChild(tableColumnTitle) + + let tableColumnYear = document.createElement('td') + tableColumnYear.setAttribute('class', 'detailTableBodyCell') + tableColumnYear.innerText = all_items[item]["year"] + tableRow.appendChild(tableColumnYear) + + let tableColumnType = document.createElement('td') + tableColumnType.setAttribute('class', 'detailTableBodyCell') + tableColumnType.innerText = translate(all_items[item]["type"].toLowerCase()) + tableRow.appendChild(tableColumnType) + + let contributeButton = document.createElement('a') + contributeButton.setAttribute('is', 'emby-linkbutton') + contributeButton.setAttribute('class', 'raised headerHelpButton button-submit emby-button') + contributeButton.setAttribute('target', '_blank') + contributeButton.setAttribute('href', all_items[item]["issue_url"]) + // text added when status is set + + let tableColumnContribute = document.createElement('td') + tableColumnContribute.appendChild(contributeButton) + tableRow.appendChild(tableColumnContribute) + + let tableColumnStatus = document.createElement('td') + tableColumnStatus.setAttribute('class', 'detailTableBodyCell') + + let statusSpan = document.createElement('span') + let statusText = document.createElement('span') + statusSpan.setAttribute('class', 'material-icons') + statusText.setAttribute('class', 'detailTableBodyCell') + if (all_items[item]["theme_provider"] === 'themerr') { + contributeButton.innerText = translate('edit_button') + statusSpan.classList.add('check') + statusSpan.setAttribute('style', 'margin-right: 6px; color: #00ff00;') + statusText.innerText = translate('themerr_provided') + } else if (all_items[item]["theme_provider"] === 'user') { + contributeButton.innerText = translate('contribute_button') + statusSpan.classList.add('person') + statusSpan.setAttribute('style', 'margin-right: 6px; color: #00a4dc;') + statusText.innerText = translate('user_provided') + } else { + contributeButton.innerText = translate('add_button') + statusSpan.classList.add('cancel') + statusSpan.setAttribute('style', 'margin-right: 6px; color: #ff0000;') + statusText.innerText = translate('no_theme_song') + } + tableColumnStatus.appendChild(statusSpan) + tableColumnStatus.appendChild(statusText) + tableRow.appendChild(tableColumnStatus) + } } function localize() { diff --git a/Locale/en.json b/Locale/en.json index 4532bb3..86e67d4 100644 --- a/Locale/en.json +++ b/Locale/en.json @@ -1,7 +1,9 @@ { "add_button": "Add", + "completion_dashboard": "Completion Dashboard", "contribute": "Contribute", "contribute_button": "Contribute", + "dashboard_loading": "Dashboard Loading...", "edit_button": "Edit", "movie": "Movie", "no_theme_song": "No theme song",