mirror of
https://github.com/rembo10/headphones.git
synced 2026-03-21 12:19:27 +00:00
481 lines
20 KiB
HTML
481 lines
20 KiB
HTML
<%inherit file="base.html" />
|
|
<%!
|
|
from headphones import db, helpers
|
|
myDB = db.DBConnection()
|
|
%>
|
|
|
|
<%def name="headerIncludes()">
|
|
<div id="subhead_container">
|
|
<div id="back_to_previous_link">
|
|
<a href="artistPage?ArtistID=${album['ArtistID']}" class="back">« Back to ${album['ArtistName']}</a>
|
|
</div>
|
|
<div id="subhead_menu">
|
|
|
|
<a id="menu_link_delete" href="deleteAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}"><i class="fa fa-trash-o"></i> Delete Album</a>
|
|
|
|
%if album['Status'] == 'Skipped' or album['Status'] == 'Ignored':
|
|
<a id="menu_link_wanted" href="#" class="album-action" data-action="queueAlbum" data-album-id="${album['AlbumID']}" data-artist-id="${album['ArtistID']}" data-new="False" data-success-msg="'${album['AlbumTitle']}' added to queue"><i class="fa fa-heart"></i> Mark Album as Wanted</a>
|
|
%elif album['Status'] == 'Wanted':
|
|
<a id="menu_link_check" href="#" class="album-action" data-action="queueAlbum" data-album-id="${album['AlbumID']}" data-artist-id="${album['ArtistID']}" data-new="True" data-success-msg="Forced checking successful"><i class="fa fa-search"></i> Force Check</a>
|
|
<a id="menu_link_skipped" href="#" class="album-action" data-action="unqueueAlbum" data-album-id="${album['AlbumID']}" data-artist-id="${album['ArtistID']}" data-success-msg="'${album['AlbumTitle']}' marked as Skipped"><i class="fa fa-step-forward"></i> Mark Album as Skipped</a>
|
|
%else:
|
|
<a id="menu_link_retry" href="#" class="album-action" data-action="queueAlbum" data-album-id="${album['AlbumID']}" data-artist-id="${album['ArtistID']}" data-new="False" data-success-msg="Retrying the same version of '${album['AlbumTitle']}'"><i class="fa fa-refresh"></i> Retry Download</a>
|
|
<a id="menu_link_new" href="#" class="album-action" data-action="queueAlbum" data-album-id="${album['AlbumID']}" data-artist-id="${album['ArtistID']}" data-new="True" data-success-msg="Looking for a new version of '${album['AlbumTitle']}'"><i class="fa fa-download"></i> Try New Version</a>
|
|
%endif
|
|
|
|
<a class="menu_link_edit dialog-trigger" id="album_chooser" href="#" data-dialog-id="dialog"><i class="fa fa-pencil"></i> Choose Alternate Release</a>
|
|
<div id="dialog" title="Choose an Alternate Release" style="display:none" class="configtable">
|
|
<div class="links">
|
|
<%
|
|
alternate_albums = myDB.select("SELECT * from allalbums WHERE AlbumID=? ORDER BY ReleaseDate ASC", [album['AlbumID']])
|
|
%>
|
|
%if not alternate_albums:
|
|
<p>No alternate releases found. Try refreshing the artist (if the artist is being refreshed, please wait until it's finished)</p>
|
|
<h2><a id="refresh_artist_btn" href="#" class="album-action" data-action="refreshArtist" data-artist-id="${album['ArtistID']}" data-success-msg="'${album['ArtistName']}' is being refreshed">Refresh Artist</a></h2>
|
|
%else:
|
|
%for alternate_album in alternate_albums:
|
|
<%
|
|
track_count = len(myDB.select("SELECT * from alltracks WHERE ReleaseID=?", [alternate_album['ReleaseID']]))
|
|
mb_link = "http://musicbrainz.org/release/" + alternate_album['ReleaseID']
|
|
have_track_count = len(myDB.select("SELECT * from alltracks WHERE ReleaseID=? AND Location IS NOT NULL", [alternate_album['ReleaseID']]))
|
|
if alternate_album['AlbumID'] == alternate_album['ReleaseID']:
|
|
alternate_album_name = "Headphones Default Release (" + str(alternate_album['ReleaseDate']) + ") [" + str(have_track_count) + "/" + str(track_count) + " tracks]"
|
|
else:
|
|
alternate_album_name = alternate_album['AlbumTitle'] + " (" + alternate_album['ReleaseCountry'] + ", " + str(alternate_album['ReleaseDate']) + ", " + alternate_album['ReleaseFormat'] + ") [" + str(have_track_count) + "/" + str(track_count) + " tracks]"
|
|
|
|
%>
|
|
<a href="#" class="album-action alternate-release-switch" data-action="switchAlbum" data-album-id="${album['AlbumID']}" data-release-id="${alternate_album['ReleaseID']}" data-success-msg="Switched release to: ${alternate_album_name}">${alternate_album_name}</a><a href="${mb_link}" target="_blank" class="external-link">MB</a><br>
|
|
%endfor
|
|
%endif
|
|
</div>
|
|
</div>
|
|
|
|
<a class="menu_link_edit dialog-trigger" id="edit_search_term" href="#" data-dialog-id="dialog2"><i class="fa fa-pencil"></i> Edit Search Term</a>
|
|
<div id="dialog2" title="Enter your own search term for this album" style="display:none" class="configtable">
|
|
<form action="editSearchTerm" method="GET" id="editSearchTermForm">
|
|
<input type="hidden" name="AlbumID" value="${album['AlbumID']}">
|
|
<div class="row">
|
|
<%
|
|
if not album['SearchTerm']:
|
|
search_term = ""
|
|
else:
|
|
search_term = album['SearchTerm']
|
|
%>
|
|
<input type="text" value="${search_term}" name="SearchTerm" size="40" />
|
|
</div>
|
|
<button type="submit" class="album-action-submit" data-action="editSearchTerm" data-success-msg="Search term updated">Save changes</button>
|
|
</form>
|
|
</div>
|
|
|
|
<a class="menu_link_edit dialog-trigger" id="choose_specific_download" href="#" data-dialog-id="choose_specific_download_dialog" data-action="getAvailableDownloads"><i class="fa fa-search"></i> Choose Specific Download</a>
|
|
<div id="choose_specific_download_dialog" title="Choose a specific download for this album" style="display:none" class="configtable">
|
|
<table class="display" id="downloads_table">
|
|
<thead>
|
|
<tr>
|
|
<th id="title">Title</th>
|
|
<th id="size">Size</th>
|
|
<th id="provider">Provider</th>
|
|
<th id="kind">Kind</th>
|
|
<th id="matches" title="Matches quality and/or seed settings.">Matches</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="downloads_table_body">
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</%def>
|
|
|
|
<%def name="body()">
|
|
<div class="table_wrapper">
|
|
<div id="albumheader" class="clearfix">
|
|
<div id="albumImg">
|
|
<img alt="${album['AlbumTitle']} album art" class="albumArt" src="artwork/album/${album['AlbumID']}">
|
|
</div>
|
|
|
|
<h1 id="albumname">
|
|
<a href="http://musicbrainz.org/release-group/${album['AlbumID']}" id="albumnamelink">${album['AlbumTitle']}</a>
|
|
</h1>
|
|
|
|
<h2 id="artistname">
|
|
<a href="http://musicbrainz.org/artist/${album['ArtistID']}" id="artistnamelink">${album['ArtistName']}</a>
|
|
</h2>
|
|
|
|
<%
|
|
totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0]
|
|
totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']]))
|
|
try:
|
|
albumduration = helpers.convert_milliseconds(totalduration)
|
|
except:
|
|
albumduration = 'n/a'
|
|
%>
|
|
<div class="albuminfo">
|
|
<div id="albumInfo"></div>
|
|
<ul>
|
|
<li>Tracks: <span>${totaltracks}</span></li>
|
|
<li>Duration: <span>${albumduration}</span></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div id="track_wrapper">
|
|
<table class="display" id="track_table">
|
|
<thead>
|
|
<tr>
|
|
<th id="number">#</th>
|
|
<th id="name">Track Title</th>
|
|
<th id="duration">Duration</th>
|
|
<th id="location">Local File</th>
|
|
<th id="bitrate">Bit Rate</th>
|
|
<th id="format">Format</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
%for track in tracks:
|
|
<%
|
|
if track['Location']:
|
|
grade = 'A'
|
|
location = track['Location']
|
|
else:
|
|
grade = 'X'
|
|
location = ''
|
|
|
|
if track['BitRate']:
|
|
bitrate = str(track['BitRate']/1000) + ' kbps'
|
|
else:
|
|
bitrate = ''
|
|
|
|
try:
|
|
trackduration = helpers.convert_milliseconds(track['TrackDuration'])
|
|
except:
|
|
trackduration = 'n/a'
|
|
|
|
if not track['Format']:
|
|
format = ''
|
|
else:
|
|
format = track['Format']
|
|
%>
|
|
<tr class="grade${grade}">
|
|
<td id="number">${track['TrackNumber']}</td>
|
|
<td id="name">${track['TrackTitle']}</td>
|
|
<td id="duration">${trackduration}</td>
|
|
<td id="location">${location}</td>
|
|
<td id="bitrate">${bitrate}</td>
|
|
<td id="format">${format}</td>
|
|
</tr>
|
|
%endfor
|
|
<%
|
|
unmatched = myDB.select('SELECT * from have WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND Matched = "Failed" ORDER BY CAST(TrackNumber AS INTEGER)', [album['ArtistName'], album['AlbumTitle']])
|
|
%>
|
|
%if unmatched:
|
|
%for track in unmatched:
|
|
<%
|
|
duration = helpers.convert_seconds(float(track['TrackLength']))
|
|
%>
|
|
<tr class="gradeC">
|
|
<td id="number">${track['TrackNumber']}</td>
|
|
<td id="name">${track['TrackTitle']}</td>
|
|
<td id="duration">${duration}</td>
|
|
<td id="location">${track['Location']}</td>
|
|
<td id="bitrate">${int(track['BitRate'])/1000} kbps</td>
|
|
<td id="format">${track['Format']}</td>
|
|
</tr>
|
|
%endfor
|
|
%endif
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</%def>
|
|
|
|
<%def name="headIncludes()">
|
|
${parent.headIncludes()} <%-- Ensure parent head includes are kept --%>
|
|
<link rel="stylesheet" href="interfaces/default/css/data_table.css">
|
|
</%def>
|
|
|
|
<%def name="javascriptIncludes()">
|
|
${parent.javascriptIncludes()} <%-- Ensure parent javascript includes are kept --%>
|
|
<script src="js/libs/jquery.dataTables.min.js"></script>
|
|
<script>
|
|
// Define a global object for page-specific functions to avoid polluting global scope directly
|
|
var AlbumPage = AlbumPage || {};
|
|
|
|
AlbumPage.search_results = []; // Moved to page-specific scope
|
|
|
|
AlbumPage.getAlbumInfo = function() {
|
|
var id = "${album['AlbumID']}";
|
|
var elem = $("#albumInfo");
|
|
// Assuming getInfo is defined in common.js
|
|
if (typeof getInfo === 'function') {
|
|
getInfo(elem,id,'album');
|
|
} else {
|
|
console.warn("getInfo function not found. Album info might not be loaded.");
|
|
}
|
|
};
|
|
|
|
AlbumPage.initDialogs = function() {
|
|
// General handler for opening dialogs based on data-dialog-id
|
|
$(document).on('click', '.dialog-trigger', function(e) {
|
|
e.preventDefault();
|
|
var dialogId = $(this).data('dialog-id');
|
|
var $dialog = $('#' + dialogId);
|
|
|
|
if ($dialog.length) {
|
|
// Specific logic for choose_specific_download_dialog before opening
|
|
if (dialogId === 'choose_specific_download_dialog') {
|
|
AlbumPage.getAvailableDownloads();
|
|
} else {
|
|
$dialog.dialog({
|
|
width: 500,
|
|
maxHeight: 500,
|
|
// Add close event to clear dynamic content if any
|
|
close: function() {
|
|
if (dialogId === 'downloads_table_body') {
|
|
$('#downloads_table_body').empty(); // Clear table content on close
|
|
// Re-initialize DataTable if needed on next open, or destroy it here
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// Specific handler for refreshing artist from alternate releases dialog
|
|
$(document).on('click', '#refresh_artist_btn', function(e) {
|
|
e.preventDefault();
|
|
var $this = $(this);
|
|
// Assuming doAjaxCall is defined in common.js
|
|
if (typeof doAjaxCall === 'function') {
|
|
doAjaxCall($this.data('action') + '?ArtistID=' + $this.data('artist-id'), $this, true, $this.data('success-msg'));
|
|
}
|
|
$('#dialog').dialog("close"); // Close dialog after action
|
|
});
|
|
};
|
|
|
|
AlbumPage.initDataTables = function() {
|
|
// Initialize track_table
|
|
$('#track_table').DataTable({ // Use .DataTable() for new versions of DataTables
|
|
"ordering": false, // aaSorting -> ordering
|
|
"searching": false, // bFilter -> searching
|
|
"info": false, // bInfo -> info
|
|
"paging": false, // bPaginate -> paging
|
|
"destroy": true // bDestroy -> destroy
|
|
});
|
|
};
|
|
|
|
AlbumPage.getAvailableDownloads = function() {
|
|
AlbumPage.ShowSpinner();
|
|
var albumId = "${album['AlbumID']}";
|
|
$.getJSON("choose_specific_download?AlbumID=" + albumId, function(data) {
|
|
AlbumPage.loader.remove(); // Assuming loader is attached to AlbumPage scope now
|
|
AlbumPage.feedback.fadeOut(); // Assuming feedback is attached to AlbumPage scope now
|
|
AlbumPage.search_results = data; // Store results
|
|
|
|
// Clear previous content
|
|
$('#downloads_table_body').empty();
|
|
|
|
for( var i = 0, len = data.length; i < len; i++ ) {
|
|
$('#downloads_table_body').append(
|
|
'<tr>' +
|
|
'<td id="title"><a href="#" class="download-specific-release-link" data-index="' + i + '">' + data[i].title + '</a></td>' +
|
|
'<td id="size"><span title="'+data[i].size+'"></span>' + (data[i].size / (1024*1024)).toFixed(2) + ' MB</td>' +
|
|
'<td id="provider">' + data[i].provider + '</td>' +
|
|
'<td id="kind">' + data[i].kind + '</td>' +
|
|
'<td id="matches">' + data[i].matches + '</td>' +
|
|
'</tr>'
|
|
);
|
|
}
|
|
|
|
// Destroy and re-initialize the DataTable for downloads
|
|
if ($.fn.DataTable.isDataTable('#downloads_table')) {
|
|
$('#downloads_table').DataTable().destroy();
|
|
}
|
|
$('#downloads_table').DataTable({
|
|
"columnDefs": [ // aoColumns -> columnDefs
|
|
{ "orderable": false, "targets": [0, 2, 3] }, // Disable ordering for Title, Provider, Kind
|
|
{ "type": "title-numeric", "targets": 1 }, // sType -> type
|
|
{ "type": "string", "targets": 4 }
|
|
],
|
|
"order": [[ 4, 'desc']], // aaSorting -> order
|
|
"searching": false,
|
|
"info": false,
|
|
"paging": false,
|
|
"destroy": true // Important for re-initialization
|
|
});
|
|
|
|
$("#choose_specific_download_dialog").dialog({
|
|
width: "80%",
|
|
maxHeight: 500,
|
|
modal: true // Added modal to make it more common practice
|
|
});
|
|
});
|
|
};
|
|
|
|
AlbumPage.downloadSpecificRelease = function(i){
|
|
var release = AlbumPage.search_results[i]; // Get from stored results
|
|
|
|
var url = "download_specific_release?AlbumID=${album['AlbumID']}" +
|
|
"&title=" + encodeURIComponent(release.title) +
|
|
"&size=" + release.size +
|
|
"&url=" + encodeURIComponent(release.url) +
|
|
"&provider=" + encodeURIComponent(release.provider) +
|
|
"&kind=" + encodeURIComponent(release.kind);
|
|
|
|
AlbumPage.ShowSpinner();
|
|
$.getJSON(url, function(data) {
|
|
AlbumPage.loader.remove();
|
|
AlbumPage.feedback.fadeOut();
|
|
// Assuming refreshSubmenu is defined globally or in common.js
|
|
if (typeof refreshSubmenu === 'function') {
|
|
refreshSubmenu();
|
|
}
|
|
$("#choose_specific_download_dialog").dialog("close");
|
|
});
|
|
};
|
|
|
|
AlbumPage.ShowSpinner = function() {
|
|
AlbumPage.feedback = $("#ajaxMsg"); // Assign to AlbumPage scope
|
|
var update = $("#updatebar");
|
|
if ( update.is(":visible") ) {
|
|
var height = update.height() + 35;
|
|
AlbumPage.feedback.css("bottom",height + "px");
|
|
} else {
|
|
AlbumPage.feedback.removeAttr("style");
|
|
}
|
|
AlbumPage.loader = $("<i class='fa fa-refresh fa-spin'></i>"); // Assign to AlbumPage scope
|
|
AlbumPage.feedback.prepend(AlbumPage.loader);
|
|
AlbumPage.feedback.fadeIn();
|
|
};
|
|
|
|
AlbumPage.loadingMessage = false;
|
|
AlbumPage.spinner_active = false;
|
|
AlbumPage.loadingtext_active = false;
|
|
AlbumPage.refreshInterval = null; // Initialize as null
|
|
AlbumPage.wasLoading = false;
|
|
AlbumPage.x = 0;
|
|
|
|
AlbumPage.checkAlbumStatus = function() {
|
|
$.getJSON("getAlbumjson?AlbumID=${album['AlbumID']}", function(data) {
|
|
if (data['Status'] === "Loading"){
|
|
AlbumPage.wasLoading = true;
|
|
$('#albumnamelink').text(data["AlbumTitle"]);
|
|
$('#artistnamelink').text(data["ArtistName"]);
|
|
if (AlbumPage.loadingMessage === false){
|
|
$("#ajaxMsg").after( "<div id='ajaxMsg2' class='ajaxMsg'></div>" );
|
|
// Assuming showArtistMsg is defined globally or in common.js
|
|
if (typeof showArtistMsg === 'function') {
|
|
showArtistMsg("Getting album information");
|
|
}
|
|
AlbumPage.loadingMessage = true;
|
|
}
|
|
if (AlbumPage.spinner_active === false){
|
|
$('#albumname').prepend('<i class="fa fa-refresh fa-spin" id="albumnamespinner"></i>');
|
|
AlbumPage.spinner_active = true;
|
|
}
|
|
if (AlbumPage.loadingtext_active === false){
|
|
$('#albumname').append('<h3 id="loadingtext"><i>(Album information is currently being loaded)</i></h3>');
|
|
AlbumPage.loadingtext_active = true;
|
|
}
|
|
} else {
|
|
AlbumPage.x++;
|
|
if (AlbumPage.x === 10 || AlbumPage.wasLoading || $("#artistname").text().trim() === "Loading") { // Combined conditions
|
|
if (AlbumPage.refreshInterval) { // Clear only if interval is set
|
|
clearInterval(AlbumPage.refreshInterval);
|
|
}
|
|
location.reload(); // Reload the page to show updated status
|
|
}
|
|
$('#albumnamespinner').remove();
|
|
$('#loadingtext').remove();
|
|
$('#ajaxMsg2').remove();
|
|
AlbumPage.spinner_active = false;
|
|
AlbumPage.loadingtext_active = false;
|
|
AlbumPage.loadingMessage = false;
|
|
}
|
|
});
|
|
};
|
|
|
|
// jQuery DataTables custom sorting
|
|
jQuery.extend( jQuery.fn.dataTableExt.oSort, {
|
|
"title-numeric-pre": function ( a ) {
|
|
// Ensure it handles cases where title attribute might be missing or different
|
|
var match = a.match(/title="*(-?[0-9\.]+)/);
|
|
return match ? parseFloat( match[1] ) : -Infinity; // Return a safe default
|
|
},
|
|
"title-numeric-asc": function ( a, b ) {
|
|
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
|
|
},
|
|
"title-numeric-desc": function ( a, b ) {
|
|
return ((a < b) ? 1 : ((a > b) ? -1 : 0));
|
|
}
|
|
});
|
|
|
|
$(document).ready(function() {
|
|
AlbumPage.getAlbumInfo();
|
|
AlbumPage.initDialogs(); // Initialize dialog triggers
|
|
AlbumPage.initDataTables(); // Initialize DataTables
|
|
// Do not use initActions() here unless it's strictly needed and modernized itself.
|
|
// setTimeout for fancybox is likely not needed if elements are ready or use delegated events.
|
|
// If fancybox is for dynamic content, initialize it after content is loaded.
|
|
// initFancybox(); // Re-evaluate if this is still needed or how it's used.
|
|
|
|
// Event handler for album actions
|
|
// Delegated to #subhead_menu as these links are within it
|
|
$('#subhead_menu').on('click', '.album-action', function(e) {
|
|
e.preventDefault();
|
|
var $this = $(this);
|
|
var action = $this.data('action');
|
|
var albumId = $this.data('album-id');
|
|
var artistId = $this.data('artist-id');
|
|
var newStatus = $this.data('new'); // Boolean 'true' or 'false'
|
|
var releaseId = $this.data('release-id');
|
|
var successMsg = $this.data('success-msg');
|
|
|
|
var url = action + '?AlbumID=' + albumId;
|
|
if (artistId) url += '&ArtistID=' + artistId;
|
|
if (newStatus !== undefined) url += '&new=' + newStatus;
|
|
if (releaseId) url += '&ReleaseID=' + releaseId;
|
|
|
|
// Assuming doAjaxCall is defined globally or in common.js
|
|
if (typeof doAjaxCall === 'function') {
|
|
doAjaxCall(url, $this, true, successMsg);
|
|
} else {
|
|
console.error("doAjaxCall function is not defined!");
|
|
}
|
|
});
|
|
|
|
// Event handler for specific download links within the dialog
|
|
// Delegated to #downloads_table_body as these links are dynamically added
|
|
$('#downloads_table_body').on('click', '.download-specific-release-link', function(e) {
|
|
e.preventDefault();
|
|
var index = $(this).data('index');
|
|
AlbumPage.downloadSpecificRelease(index);
|
|
});
|
|
|
|
// Event handler for form submission within dialogs
|
|
// Delegated to the specific form's ID
|
|
$('#editSearchTermForm').on('submit', function(e) {
|
|
e.preventDefault(); // Prevent default form submission
|
|
var $this = $(this);
|
|
var action = $this.attr('action'); // Get action from form
|
|
var formData = $this.serialize(); // Serialize form data
|
|
var successMsg = $this.find('.album-action-submit').data('success-msg');
|
|
|
|
// Assuming doAjaxCall is defined globally or in common.js
|
|
if (typeof doAjaxCall === 'function') {
|
|
// Pass form data instead of element, specify 'form' type if common.js handles it
|
|
doAjaxCall(action + '?' + formData, $this.find('.album-action-submit'), true, successMsg);
|
|
}
|
|
$('#dialog2').dialog("close"); // Close dialog after submission
|
|
});
|
|
|
|
|
|
// Start checking album status periodically
|
|
AlbumPage.checkAlbumStatus();
|
|
AlbumPage.refreshInterval = setInterval(function(){
|
|
AlbumPage.checkAlbumStatus();
|
|
}, 3000);
|
|
});
|
|
|
|
</script>
|
|
</%def>
|