From 393d7a5197a69f7d79a45bcb07d29ed519d985d6 Mon Sep 17 00:00:00 2001 From: rern Date: Fri, 7 Oct 2022 21:08:25 +0700 Subject: [PATCH 01/36] Update features.js --- srv/http/assets/js/features.js | 1 + 1 file changed, 1 insertion(+) diff --git a/srv/http/assets/js/features.js b/srv/http/assets/js/features.js index 8dc7fda13..c49a1e4b9 100644 --- a/srv/http/assets/js/features.js +++ b/srv/http/assets/js/features.js @@ -390,6 +390,7 @@ $( '#nfsserver' ).click( function() { , title : 'Server rAudio' , message : $this.prev().html() } ); + $this.prop( 'checked', G.nfsserver ); return } From 62853e6e41139e828f8ab7bb40bd448cab7fd59c Mon Sep 17 00:00:00 2001 From: rern Date: Sat, 8 Oct 2022 08:19:21 +0700 Subject: [PATCH 02/36] Update status-push.sh --- srv/http/bash/status-push.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/srv/http/bash/status-push.sh b/srv/http/bash/status-push.sh index b697ee552..4bf2e9028 100644 --- a/srv/http/bash/status-push.sh +++ b/srv/http/bash/status-push.sh @@ -102,3 +102,6 @@ fi $Artist $Title $Album" &> /dev/null & + +[[ $state == play ]] && playing=true || playing=false +pushstream refresh '{"page":"features","playing":'$playing'}' From 97b414cf09248482b35d3b5d49db816268a019b1 Mon Sep 17 00:00:00 2001 From: rern Date: Sat, 8 Oct 2022 08:23:45 +0700 Subject: [PATCH 03/36] Update status-push.sh --- srv/http/bash/status-push.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/srv/http/bash/status-push.sh b/srv/http/bash/status-push.sh index 4bf2e9028..a8f57e2ac 100644 --- a/srv/http/bash/status-push.sh +++ b/srv/http/bash/status-push.sh @@ -91,6 +91,9 @@ fi [[ -e $dirsystem/librandom && $webradio == false ]] && $dirbash/cmd.sh mpcaddrandom +[[ $state == play ]] && playing=true || playing=false +pushstream refresh '{"page":"features","playing":'$playing'}' + [[ ! $scrobble ]] && exit # must be last for $statusprev - webradio and state . <( echo "$statusprev" ) @@ -102,6 +105,3 @@ fi $Artist $Title $Album" &> /dev/null & - -[[ $state == play ]] && playing=true || playing=false -pushstream refresh '{"page":"features","playing":'$playing'}' From d55744e352a2c5fe6b8a3e5e4b8acc0c57c7802b Mon Sep 17 00:00:00 2001 From: rern Date: Sat, 8 Oct 2022 08:52:40 +0700 Subject: [PATCH 04/36] u --- srv/http/bash/settings/features.sh | 8 ++++---- srv/http/mpdplaylist.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/srv/http/bash/settings/features.sh b/srv/http/bash/settings/features.sh index 291f26f05..476ac408e 100644 --- a/srv/http/bash/settings/features.sh +++ b/srv/http/bash/settings/features.sh @@ -297,7 +297,6 @@ nfsserver ) if [[ -e $dirbackup/mpdnfs ]]; then mv -f $dirmpd $dirbackup mv -f $dirbackup/mpdnfs $dirdata/mpd - systemctl restart mpd action=update else rm -f $dirmpd/{listing,updating} @@ -306,8 +305,8 @@ nfsserver ) action=rescan fi systemctl enable --now nfs-server - $dirbash/cmd.sh mpcupdate$'\n'$action else + action=update systemctl disable --now nfs-server rm -f /mnt/MPD/.mpdignore \ /mnt/MPD/NAS/.mpdignore \ @@ -323,9 +322,10 @@ nfsserver ) mv -f $dirmpd $dirbackup/mpdnfs mv -f $dirbackup/mpd $dirdata mv -f $dirbackup/{display,order} $dirsystem - systemctl restart mpd - $dirbash/cmd.sh mpcupdate$'\n'update fi + mpc -q clear + systemctl restart mpd + $dirbash/cmd.sh mpcupdate$'\n'$action pushRefresh pushstream refresh '{"page":"system","nfsserver":'$active'}' ;; diff --git a/srv/http/mpdplaylist.php b/srv/http/mpdplaylist.php index d896639dc..111989fa0 100644 --- a/srv/http/mpdplaylist.php +++ b/srv/http/mpdplaylist.php @@ -35,7 +35,7 @@ htmlSavedPlaylist(); break; case 'load': // load saved playlist to current - if ( $_POST[ 'replace' ] ) exec( 'mpc clear' ); + if ( $_POST[ 'replace' ] ) exec( 'mpc -q clear' ); $name = $_POST[ 'name' ] ?? $argv[ 2 ]; // $argv - by import playlists exec( 'mpc -q load "'.$name.'"' ); From a3902e77f44da62b1c1ad3d51d6d5c3d7c0e39ed Mon Sep 17 00:00:00 2001 From: rern Date: Sat, 8 Oct 2022 08:59:24 +0700 Subject: [PATCH 05/36] Update features.sh --- srv/http/bash/settings/features.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/srv/http/bash/settings/features.sh b/srv/http/bash/settings/features.sh index 476ac408e..884f23e7a 100644 --- a/srv/http/bash/settings/features.sh +++ b/srv/http/bash/settings/features.sh @@ -278,6 +278,7 @@ EOF nfsserver ) active=${args[1]} readarray -t dirs <<< $( nfsShareList ) + mpc -q clear if [[ $active == true ]]; then ip=$( ipGet ) options="${ip%.*}.0/24(rw,sync,no_subtree_check)" @@ -297,16 +298,16 @@ nfsserver ) if [[ -e $dirbackup/mpdnfs ]]; then mv -f $dirmpd $dirbackup mv -f $dirbackup/mpdnfs $dirdata/mpd - action=update + systemctl restart mpd else rm -f $dirmpd/{listing,updating} mkdir -p $dirbackup cp -r $dirmpd $dirbackup - action=rescan + systemctl restart mpd + $dirbash/cmd.sh mpcupdate$'\n'rescan fi systemctl enable --now nfs-server else - action=update systemctl disable --now nfs-server rm -f /mnt/MPD/.mpdignore \ /mnt/MPD/NAS/.mpdignore \ @@ -322,10 +323,8 @@ nfsserver ) mv -f $dirmpd $dirbackup/mpdnfs mv -f $dirbackup/mpd $dirdata mv -f $dirbackup/{display,order} $dirsystem + systemctl restart mpd fi - mpc -q clear - systemctl restart mpd - $dirbash/cmd.sh mpcupdate$'\n'$action pushRefresh pushstream refresh '{"page":"system","nfsserver":'$active'}' ;; From f8226a6d09932fb518bccb56c2abbd9cdc87ca82 Mon Sep 17 00:00:00 2001 From: rern Date: Sat, 8 Oct 2022 14:10:22 +0700 Subject: [PATCH 06/36] Update install.sh --- install.sh | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/install.sh b/install.sh index 80e0d5c56..94a9782ba 100644 --- a/install.sh +++ b/install.sh @@ -2,21 +2,14 @@ alias=r1 -# 20221005 +# 20221007 dir=/srv/http/shareddata dirshareddata=/mnt/MPD/NAS/data filesharedip=$dirshareddata/sharedip if [[ -e $dir ]]; then - if [[ -e $filesharedip ]]; then - list=$( cat $filesharedip ) - else - echo data > /mnt/MPD/NAS/.mpdignore - mkdir -p $dirshareddata - list=$( grep $dir /etc/fstab | sed 's|^//||; s|/.*||; s|:.*||' ) - fi - echo "\ -$list -$( ifconfig | grep -m1 inet.*broadcast | awk '{print $2}' )" | sort -u > $filesharedip + echo data > /mnt/MPD/NAS/.mpdignore + mkdir -p $dirshareddata + mv $dir/iplist > $filesharedip chmod 777 $filesharedip umount -l $dir sed -i "s|$dir|$dirshareddata|" /etc/fstab From 4041269f6b38fb460d1dba1ce00602f19e65fa9b Mon Sep 17 00:00:00 2001 From: rern Date: Sat, 8 Oct 2022 17:25:04 +0700 Subject: [PATCH 07/36] Update system.sh --- srv/http/bash/settings/system.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srv/http/bash/settings/system.sh b/srv/http/bash/settings/system.sh index ca12d61e2..c7840883c 100644 --- a/srv/http/bash/settings/system.sh +++ b/srv/http/bash/settings/system.sh @@ -525,7 +525,7 @@ mount ) options+=,uid=$( id -u mpd ),gid=$( id -g mpd ),iocharset=utf8 else source="$ip:$directory" - options=defaults,noauto,bg,hard,intr,timeo=5 + options=defaults,noauto,bg,soft,timeo=5 fi [[ $extraoptions ]] && options+=,$extraoptions fstab="\ @@ -730,7 +730,7 @@ shareddataconnect ) [[ $( ls "$dir" ) ]] && echo "Directory not empty: $dir" && exit done - options="nfs defaults,noauto,bg,hard,intr,timeo=5 0 0" + options="nfs defaults,noauto,bg,soft,timeo=5 0 0" fstab=$( cat /etc/fstab ) for path in "${paths[@]}"; do dir="/mnt/MPD/NAS/$( basename "$path" )" From 98b9dfeeda0bfacd8f0cced70ee3cbf68047ac7b Mon Sep 17 00:00:00 2001 From: rern Date: Sat, 8 Oct 2022 17:27:44 +0700 Subject: [PATCH 08/36] Update install.sh --- install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/install.sh b/install.sh index 94a9782ba..bcc4be253 100644 --- a/install.sh +++ b/install.sh @@ -3,6 +3,8 @@ alias=r1 # 20221007 +grep -q hard,intr /etc/fstab && sed -i '/hard,intr/soft/' /etc/fstab + dir=/srv/http/shareddata dirshareddata=/mnt/MPD/NAS/data filesharedip=$dirshareddata/sharedip From eda96aa14336478501eae35d7a0de91b65be08af Mon Sep 17 00:00:00 2001 From: rern Date: Sat, 8 Oct 2022 19:30:49 +0700 Subject: [PATCH 09/36] u --- srv/http/assets/css/common.css | 2 +- srv/http/assets/fonts/rern.woff2 | Bin 16496 -> 16780 bytes srv/http/bash/status.sh | 6 +++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/srv/http/assets/css/common.css b/srv/http/assets/css/common.css index 1c46ae13b..7ead0b4f4 100644 --- a/srv/http/assets/css/common.css +++ b/srv/http/assets/css/common.css @@ -1,6 +1,6 @@ @font-face { font-family: rern; - src: url( '/assets/fonts/rern.2022100300.woff2' ) format( 'woff2' ); + src: url( '/assets/fonts/rern.2022101000.woff2' ) format( 'woff2' ); font-display: block; font-style: normal; font-weight: normal; diff --git a/srv/http/assets/fonts/rern.woff2 b/srv/http/assets/fonts/rern.woff2 index d48a8fb3fa7b9ab7a05406d5543ea445f841a446..114c0eccdfa9b294974b9b873c8c7bc9309601c8 100644 GIT binary patch literal 16780 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≫6As#9#3_!WINttCmR@Zu(EN0M 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 b?+BX(#?DW01OQtw1t12>7D)d`egFUfEzmro diff --git a/srv/http/bash/status.sh b/srv/http/bash/status.sh index 60c8f8d5b..795785ab2 100644 --- a/srv/http/bash/status.sh +++ b/srv/http/bash/status.sh @@ -331,10 +331,10 @@ $radiosampling" > $dirshm/radio if [[ $displaycover ]]; then filenoext=/data/webradio/img/$urlname pathnoext=/srv/http$filenoext - if [[ -e $pathnoext.gif ]]; then - stationcover=$filenoext.$date.gif - elif [[ -e $pathnoext.jpg ]]; then + if [[ -e $pathnoext.jpg ]]; then stationcover=$filenoext.$date.jpg + elif [[ -e $pathnoext.gif ]]; then + stationcover=$filenoext.$date.gif fi fi status=$( grep -E -v '^, *"state"|^, *"webradio".*true|^, *"webradio".*false' <<< "$status" ) From b91826f8e740ce7094d879be0511176ddabae22d Mon Sep 17 00:00:00 2001 From: rern Date: Sat, 8 Oct 2022 21:24:17 +0700 Subject: [PATCH 10/36] Update system-datareset.sh --- srv/http/bash/settings/system-datareset.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srv/http/bash/settings/system-datareset.sh b/srv/http/bash/settings/system-datareset.sh index 9c9d48422..006301450 100644 --- a/srv/http/bash/settings/system-datareset.sh +++ b/srv/http/bash/settings/system-datareset.sh @@ -146,7 +146,7 @@ sed -i -e -E '/^auto_update|^audio_buffer_size| #custom$/ d quality "very high"\ } ' /etc/mpd.conf -curl -L https://github.com/rern/rAudio-addons/raw/main/webradio/radioparadise.tar.xz | bsdtar xvf - -C / # webradio default +curl -L https://github.com/rern/rAudio-addons/raw/main/webradio/radioparadise.tar.xz | bsdtar xvf - -C $dirwebradio # webradio default if [[ ! -e $dirmpd/counts ]]; then echo '{ "playlists" : '$( ls -1 $dirplaylists | wc -l )' From f90906f3c2dc6f3a0e8cdee74802e73736dc238b Mon Sep 17 00:00:00 2001 From: rern Date: Sat, 8 Oct 2022 21:36:24 +0700 Subject: [PATCH 11/36] Update main.js --- srv/http/assets/js/main.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/srv/http/assets/js/main.js b/srv/http/assets/js/main.js index 89cc042ba..84eca43eb 100644 --- a/srv/http/assets/js/main.js +++ b/srv/http/assets/js/main.js @@ -1838,8 +1838,10 @@ $( '#button-pl-clear' ).click( function() { , buttoncolor : [ orange ] , button : [ function() { - $( '#pl-list .li1' ).before( '' ); - $( '#pl-list .name' ).css( 'max-width', 'calc( 100% - 135px )' ); + setTimeout( function() { // must be delayed + $( '#pl-list .li1' ).before( '' ); + $( '#pl-list .name' ).css( 'max-width', 'calc( 100% - 135px )' ); + }, 0 ); } , function() { $( '#pl-list li:not( .active )' ).remove(); From 0045e5cc96010917b7c73d37bee1bf20d1a5555f Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 07:52:09 +0700 Subject: [PATCH 12/36] Add files via upload --- srv/http/bash/settings/system-data.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/srv/http/bash/settings/system-data.sh b/srv/http/bash/settings/system-data.sh index 47b9fd189..2cda6f6a1 100644 --- a/srv/http/bash/settings/system-data.sh +++ b/srv/http/bash/settings/system-data.sh @@ -104,7 +104,10 @@ if [[ $usb ]]; then [[ ! $label ]] && label=? list+=',{"icon":"usbdrive","mountpoint":"/mnt/MPD/USB/'$label'","mounted":false,"source":"'$source'"}' fi - [[ ! $hddapm ]] && hddapm=$( hdparm -B $source | grep -m1 APM_level | awk '{print $NF}' ) + [[ ! $hddapm ]] && hddapm=$( hdparm -B $source \ + | grep -m1 APM_level \ + | awk '{print $NF}' \ + | tr -d -c 0-9 ) done fi nas=$( awk '/.mnt.MPD.NAS|.srv.http.data/ {print $1" "$2}' /etc/fstab | sort ) From 6a667dfb1c54eb721ffe7abbc85f8f95566d8552 Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 08:46:31 +0700 Subject: [PATCH 13/36] Update system.php --- srv/http/settings/system.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srv/http/settings/system.php b/srv/http/settings/system.php index c3a2dad62..5a562c9f8 100644 --- a/srv/http/settings/system.php +++ b/srv/http/settings/system.php @@ -113,7 +113,7 @@

  'HDD Sleep'
+	  'label'    => 'Hard Drive Sleep'
 	, 'id'       => 'hddsleep'
 	, 'icon'     => 'screenoff'
 	, 'disabled' => 'HDD not support sleep'
@@ -124,7 +124,7 @@
 htmlSetting( [
 	  'label'    => 'Hotplug Update'
 	, 'id'       => 'usbautoupdate'
-	, 'sublabel' => 'USB drives data'
+	, 'sublabel' => 'data on USB'
 	, 'icon'     => 'refresh-library'
 	, 'setting'  => false
 	, 'disabled' => $disabledusbautoupdate

From b2adb06b37a4342d37224ffebc5afc9f35c64a05 Mon Sep 17 00:00:00 2001
From: rern 
Date: Sun, 9 Oct 2022 10:26:41 +0700
Subject: [PATCH 14/36] u

---
 srv/http/assets/js/info.js   | 65 +++++++++++++++++-------------------
 srv/http/assets/js/main.js   |  6 ++--
 srv/http/assets/js/system.js |  4 +--
 3 files changed, 33 insertions(+), 42 deletions(-)

diff --git a/srv/http/assets/js/info.js b/srv/http/assets/js/info.js
index 23a5d61b4..a48671729 100644
--- a/srv/http/assets/js/info.js
+++ b/srv/http/assets/js/info.js
@@ -163,34 +163,34 @@ info( {                                       // default
 	
 	order         : [ TYPE, ... ]             // (sequence)     (order of *** inputs)
 	
+	values        : [ 'VALUE', ... ]          // (none)         (default values - in layout order)
+	checkchanged  : 1                         // (none)         (check values changed)
+	checkblank    : 1 or [ i, ... ]           // (none)         (check values not blank /  [ partial ] )
+	checklength   : { i: N, . }               // (none)         (required N characters in i)
+	checklength   : { i: [ N, 'COND' ], ... } // (none)         (required N: characters; COND: min, max; in i)
+	
+	beforeshow    : FUNCTION                  // (none)         (function after values set)
+	noreload      : 1                         // (none)         (do not reset content - for update value)
+	
 	filelabel     : 'LABEL'                   // ***            (browse button label)
 	fileoklabel   : 'LABEL'                   // 'OK'           (upload button label)
 	filetype      : '.EXT, ...'               // (none)         (filter and verify filetype (with 'dot' - 'image/*' for all image types)
 	
-	buttonlabel   : [ 'LABEL', ... ]          // ***            (label array)
+	buttonlabel   : [ 'LABEL', ... ]          // ***            (extra buttons - label array)
 	button        : [ FUNCTION, ... ]         // (none)         (function array)
 	buttoncolor   : [ 'COLOR', ... ]          // 'var( --cm )'  (color array)
 	buttonfit     : 1                         // (none)         (fit buttons width to label)
-	buttonnoreset : 1                         // (none)         (do not hide/reset content on button clicked)
-	
-	okno          : 1                         // (show)         (no ok button)
-	oklabel       : 'LABEL'                   // ('OK')         (ok button label)
-	okcolor       : 'COLOR'                   // var( --cm )    (ok button color)
-	ok            : FUNCTION                  // (reset)        (ok click function)
+	buttonnoreset : 1                         // (none)         (do not hide/reset content on button clicked) - player.js
 	
 	cancellabel   : 'LABEL'                   // ***            (cancel button label)
 	cancelcolor   : 'COLOR'                   // var( --cg )    (cancel button color)
 	cancelshow    : 1                         // (hide)         (show cancel button)
 	cancel        : FUNCTION                  // (reset)        (cancel click function)
-	
-	values        : [ 'VALUE', ... ]          // (none)         (default values - in layout order)
-	checkchanged  : 1                         // (none)         (check values changed)
-	checkblank    : 1 or [ i, ... ]           // (none)         (check values not blank /  [ partial ] )
-	checklength   : { i: N, . }               // (none)         (required N characters in i)
-	checklength   : { i: [ N, 'COND' ], ... } // (none)         (required N: characters; COND: min, max; in i)
-	
-	beforeshow    : FUNCTION                  // (none)         (function after values set)
-	noreload      : 1                         // (none)         (do not reset content - for update value)
+
+	okno          : 1                         // (show)         (no ok button)
+	oklabel       : 'LABEL'                   // ('OK')         (ok button label)
+	okcolor       : 'COLOR'                   // var( --cm )    (ok button color)
+	ok            : FUNCTION                  // (reset)        (ok click function)
 } );
 
 Get values: infoVal()
@@ -201,12 +201,14 @@ Note:
 - Single value/function - no need to be array
 ` );
 }
-function infoReset() {
+function infoReset( fn ) {
 	if ( O.infoscroll ) $( 'html, body' ).scrollTop( O.infoscroll );
-	$( '#infoOverlay' )
-		.addClass( 'hide' )
-		.empty();
-	O = {}
+	if ( !O.buttonnoreset ) {
+		$( '#infoOverlay' )
+			.addClass( 'hide' )
+			.empty();
+	}
+	if ( typeof fn === 'function' ) setTimeout( fn, 0 );
 }
 
 O = {}
@@ -222,15 +224,14 @@ function info( json ) {
 
 ` );
 	O.infoscroll = $( window ).scrollTop();
-	// simple use as info( 'message' )
-	setTimeout( function() { // allow consecutive infos
-	//////////////////////////////////////////////////////////////////////////
+	
 /*	$( '#infoOverlay' ).on( 'mousedown touchstart', function( e ) {
 		if ( e.target.id === 'infoOverlay' ) $( '#infoX' ).click();
 	} );*/
+	
 	$( '#infoX' ).click( function() {
-		if ( O.cancel ) O.cancel();
-		infoReset();
+		delete O.buttonnoreset;
+		infoReset( O.cancel );
 	} );
 	if ( typeof O !== 'object' ) {
 		$( '#infoIcon' ).addClass( 'fa fa-info-circle' );
@@ -286,18 +287,14 @@ function info( json ) {
 	if ( O.button ) {
 		if ( typeof O.button !== 'object' ) O.button = [ O.button ];
 		$( '#infoButtons' ).on( 'click', '.extrabtn', function() {
-			var fn = O.button[ $( this ).index( '.extrabtn' ) ];
-			if ( fn ) fn();
-			if ( !O.buttonnoreset ) infoReset();
+			infoReset( O.button[ $( this ).index( '.extrabtn' ) ] );
 		} );
 	}
 	$( '#infoCancel' ).one( 'click', function() {
-		if ( typeof O.cancel === 'function' ) O.cancel();
-		infoReset();
+		infoReset( O.cancel );
 	} );
 	$( '#infoOk' ).one( 'click', function() {
-		if ( typeof O.ok === 'function' ) O.ok();
-		if ( !O.buttonnoreset ) infoReset();
+		infoReset( O.ok );
 	} );
 	if ( O.fileoklabel ) {
 		var htmlfile = '
' @@ -568,8 +565,6 @@ function info( json ) { O.nochange = O.values && O.checkchanged ? true : false; $( '#infoOk' ).toggleClass( 'disabled', O.blank || O.short || O.nochange ); // initial check infoCheckSet(); - ////////////////////////////////////////////////////////////////////////// - }, 0 ); } function checkBlank() { diff --git a/srv/http/assets/js/main.js b/srv/http/assets/js/main.js index 84eca43eb..89cc042ba 100644 --- a/srv/http/assets/js/main.js +++ b/srv/http/assets/js/main.js @@ -1838,10 +1838,8 @@ $( '#button-pl-clear' ).click( function() { , buttoncolor : [ orange ] , button : [ function() { - setTimeout( function() { // must be delayed - $( '#pl-list .li1' ).before( '' ); - $( '#pl-list .name' ).css( 'max-width', 'calc( 100% - 135px )' ); - }, 0 ); + $( '#pl-list .li1' ).before( '' ); + $( '#pl-list .name' ).css( 'max-width', 'calc( 100% - 135px )' ); } , function() { $( '#pl-list li:not( .active )' ).remove(); diff --git a/srv/http/assets/js/system.js b/srv/http/assets/js/system.js index 8940faa04..03b76d575 100644 --- a/srv/http/assets/js/system.js +++ b/srv/http/assets/js/system.js @@ -1023,9 +1023,7 @@ function infoNFSconnect( ip ) { $( '#shareddata' ).prop( 'checked', false ); } , ok : function() { - setTimeout( function() { - infoNFSconnect( ip ); - },0 ); + infoNFSconnect( ip ); } } ); } From 6b44decca456e7e1a7891df3b9c854ff125589a8 Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 13:13:32 +0700 Subject: [PATCH 15/36] Update system.sh --- srv/http/bash/settings/system.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/srv/http/bash/settings/system.sh b/srv/http/bash/settings/system.sh index c7840883c..f9d31f507 100644 --- a/srv/http/bash/settings/system.sh +++ b/srv/http/bash/settings/system.sh @@ -867,7 +867,10 @@ systemconfig ) $( cat /boot/cmdline.txt ) # cat /boot/config.txt -$( cat /boot/config.txt )" +$( cat /boot/config.txt ) + +# bootloader and firmware +$( pacman -Q firmware-raspberrypi linux-firmware raspberrypi-bootloader raspberrypi-firmware )" file=/etc/modules-load.d/raspberrypi.conf raspberrypiconf=$( cat $file ) if [[ $raspberrypiconf ]]; then From e82064cefdfe55304285365d9bc96d28e7e9a258 Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 13:39:18 +0700 Subject: [PATCH 16/36] Add files via upload --- srv/http/bash/cmd.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/srv/http/bash/cmd.sh b/srv/http/bash/cmd.sh index 8cfdf7a3a..1505fccce 100644 --- a/srv/http/bash/cmd.sh +++ b/srv/http/bash/cmd.sh @@ -232,6 +232,7 @@ stopRadio() { if [[ -e $dirshm/radio ]]; then systemctl stop radio dab rm -f $dirshm/{radio,status} + sleep 1 fi } urldecode() { # for webradio url to filename @@ -873,12 +874,12 @@ mpcplayback ) command=play fi fi + stopRadio if [[ $command == play ]]; then mpc | grep -q '^\[paused\]' && pause=1 mpc -q $command $pos [[ $( mpc | head -c 4 ) == cdda && ! $pause ]] && pushstreamNotifyBlink 'Audio CD' 'Start play ...' audiocd else - stopRadio [[ -e $dirsystem/scrobble && $command == stop && $pos ]] && cp -f $dirshm/{status,scrobble} mpc -q $command killall cava &> /dev/null From 5a4e9f8d6b4b9685fb0203c3aba3e2394f0007e8 Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 14:55:04 +0700 Subject: [PATCH 17/36] u --- install.sh | 17 ++-- srv/http/assets/js/settings.js | 52 +++++----- srv/http/assets/js/system.js | 4 +- srv/http/bash/cmd.sh | 110 --------------------- srv/http/bash/settings/system-datareset.sh | 2 +- srv/http/bash/settings/system.sh | 89 ++++++++++++++++- srv/http/bash/startup.sh | 2 +- 7 files changed, 126 insertions(+), 150 deletions(-) diff --git a/install.sh b/install.sh index bcc4be253..acc7f15cc 100644 --- a/install.sh +++ b/install.sh @@ -2,12 +2,12 @@ alias=r1 +. /srv/http/bash/addons.sh + # 20221007 grep -q hard,intr /etc/fstab && sed -i '/hard,intr/soft/' /etc/fstab dir=/srv/http/shareddata -dirshareddata=/mnt/MPD/NAS/data -filesharedip=$dirshareddata/sharedip if [[ -e $dir ]]; then echo data > /mnt/MPD/NAS/.mpdignore mkdir -p $dirshareddata @@ -20,7 +20,7 @@ if [[ -e $dir ]]; then rmdir $dir fi -[[ -e /srv/http/data/system/hddspindown ]] && mv /srv/http/data/system/{hddspindown,apm} +[[ -e $dirsystem/hddspindown ]] && mv $dirsystem/{hddspindown,apm} if [[ ! -e /boot/kernel.img ]]; then dir=/etc/systemd/system @@ -60,11 +60,10 @@ fi systemctl daemon-reload # 20220916 -dirmpd=/srv/http/data/mpd if (( $( cat $dirmpd/counts | wc -l ) == 1 )); then echo '{ - "playlists" : '$( ls -1 /srv/http/data/playlists | wc -l )' -, "webradio" : '$( find -L /srv/http/data/webradio -type f ! -path '*/img/*' | wc -l )' + "playlists" : '$( ls -1 $dirplaylists | wc -l )' +, "webradio" : '$( find -L $dirwebradio -type f ! -path '*/img/*' | wc -l )' }' > $dirmpd/counts fi @@ -75,14 +74,12 @@ fi rm /srv/http/bash/{camilladsp*,features*,networks*,player*,relays*,system*} &> /dev/null #------------------------------------------------------------------------------- -. /srv/http/bash/addons.sh - installstart "$1" getinstallzip -chmod +x $dirbash/cmd.sh -$dirbash/cmd.sh dirpermissions +chmod +x $dirbash/settings/system.sh +$dirbash/settings/system.sh dirpermissions [[ -e $dirsystem/color ]] && $dirbash/cmd.sh color installfinish diff --git a/srv/http/assets/js/settings.js b/srv/http/assets/js/settings.js index dae81a6bf..63f7a82f0 100644 --- a/srv/http/assets/js/settings.js +++ b/srv/http/assets/js/settings.js @@ -2,9 +2,12 @@ function bash( command, callback, json ) { if ( typeof command === 'string' ) { var args = { cmd: 'bash', bash : command } } else { - if ( command[ 0 ] === 'cmd' ) { + var cmd0 = command[ 0 ]; + if ( cmd0 === 'cmd' ) { var filesh = 'cmd'; command.shift(); + } else if ( cmd0 === 'pkgstatus' ) { + var filesh = 'settings/system'; } else { var filesh = 'settings/'+ page; } @@ -67,7 +70,7 @@ function currentStatus( id, refresh ) { notify( 'Get Data', id, page ); }, 1000 ); } - var command = services.includes( id ) ? [ 'cmd', 'pkgstatus', id ] : cmd[ id ]+' 2> /dev/null'; + var command = services.includes( id ) ? [ 'pkgstatus', id ] : cmd[ id ]+' 2> /dev/null'; bash( command, function( status ) { clearTimeout( timeoutGet ); $el.html( status ).promise().done( function() { @@ -416,30 +419,29 @@ $( '.close' ).click( function() { if ( page === 'networks' ) { clearTimeout( G.timeoutScan ); bash( 'killall networks-scan.sh &> /dev/null' ); + } else if ( page === 'system' ) { + bash( [ 'rebootlist' ], function( list ) { + if ( !list ) { + location.href = '/'; + } else { + info( { + icon : page + , title : 'System Setting' + , message : 'Reboot required for:

' + +'
'+ list +'
' + , cancel : function() { + bash( 'rm -f /srv/http/data/shm/reboot /srv/http/data/tmp/backup.*' ); + location.href = '/'; + } + , okcolor : orange + , oklabel : 'Reboot' + , ok : function() { + bash( [ 'cmd', 'power', 'reboot' ] ); + } + } ); + } + } ); } - bash( [ 'cmd', 'rebootlist' ], function( list ) { - if ( !list ) { - location.href = '/'; - } else { - var list = list.replace( /\^/s, '\n' ); - info( { - icon : page - , title : 'System Setting' - , message : `\ -Reboot required for: -${ list }` - , cancel : function() { - bash( 'rm -f /srv/http/data/shm/reboot /srv/http/data/tmp/backup.*' ); - location.href = '/'; - } - , okcolor : orange - , oklabel : 'Reboot' - , ok : function() { - bash( [ 'cmd', 'power', 'reboot' ] ); - } - } ); - } - } ); } ); $( '#button-data' ).click( function() { if ( !G ) return diff --git a/srv/http/assets/js/system.js b/srv/http/assets/js/system.js index 03b76d575..3d7a2b934 100644 --- a/srv/http/assets/js/system.js +++ b/srv/http/assets/js/system.js @@ -512,9 +512,9 @@ $( '#setting-mpdoled' ).click( function() { , cancel : function() { $( '#mpdoled' ).prop( 'checked', G.mpdoled ); } - , buttonlabel : 'Logo' + , buttonlabel : !G.mpdoled ? '' : 'Logo' , button : !G.mpdoled ? '' : function() { - bash( '/srv/http/bash/cmd.sh mpdoledlogo' ); + bash( [ 'mpdoledlogo' ] ); } , ok : function() { notify( 'Spectrum OLED', G.mpdoled ? 'Change ...' : 'Enable ...', 'mpdoled' ); diff --git a/srv/http/bash/cmd.sh b/srv/http/bash/cmd.sh index 1505fccce..f3e073689 100644 --- a/srv/http/bash/cmd.sh +++ b/srv/http/bash/cmd.sh @@ -114,11 +114,6 @@ jpgThumbnail() { esac pushstreamImage "$target" $type "$covername" } -mpdoledLogo() { - systemctl stop mpd_oled - type=$( grep mpd_oled /etc/systemd/system/mpd_oled.service | cut -d' ' -f3 ) - mpd_oled -o $type -L -} plAddPlay() { pushstreamPlaylist add if [[ ${1: -4} == play ]]; then @@ -365,12 +360,6 @@ webRadioSampling() { case ${args[0]} in -addonsclose ) - script=${args[1]} - alias=${args[2]} - killall $script curl pacman &> /dev/null - rm -f /var/lib/pacman/db.lck /srv/http/*.zip $diraddons/$alias /usr/local/bin/uninstall_$alias.sh - ;; addonslist ) addonsListGet ${args[1]} bash=$( jq -r .push.bash $diraddons/addons-list.json ) # push bash @@ -523,9 +512,6 @@ s|(path.*hsl).*|\1(${hsg}75%);}| sed -i -E 's/\?v=.{10}/?v='$hash'/g' /srv/http/settings/camillagui/build/index.html pushstream reload 1 ;; -count ) - count - ;; coverartget ) path=${args[1]} coverartfile=$( ls -1X "$path"/coverart.* 2> /dev/null \ @@ -614,20 +600,6 @@ dabscan ) $dirbash/dab-scan.sh &> /dev/null & pushstream mpdupdate '{"type":"dabradio"}' ;; -dirpermissions ) - chmod 755 /srv /srv/http /srv/http/* /mnt /mnt/MPD /mnt/MPD/*/ - chown http:http /srv /srv/http /srv/http/* /mnt /mnt/MPD /mnt/MPD/*/ - chmod -R 755 /srv/http/{assets,bash,data,settings} - chown -R http:http /srv/http/{assets,bash,data,settings} - chown mpd:audio $dirmpd $dirmpd/mpd.db $dirplaylists 2> /dev/null - if [[ $( readlink $dirshareddata ) == $dirdata ]]; then - chmod 777 $filesharedip $dirshareddata/system/{display,order} - readarray -t dirs <<< $( showmount --no-headers -e localhost | awk 'NF{NF-=1};1' ) - for dir in "${dirs[@]}"; do - chmod 777 "$dir" - done - fi - ;; displaysave ) data=${args[1]} pushstream display "$data" @@ -990,84 +962,11 @@ mpcupdate ) [[ $path == rescan ]] && mpc -q rescan || mpc -q update "$path" pushstream mpdupdate '{"type":"mpd"}' ;; -mpdoledlogo ) - mpdoledLogo - ;; ordersave ) data=$( jq <<< ${args[1]} ) pushstream order "$data" echo "$data" > $dirsystem/order ;; -partexpand ) - dev=$( mount | awk '/ on \/ / {printf $1}' | head -c -2 ) - if (( $( sfdisk -F $dev | head -1 | awk '{print $6}' ) != 0 )); then - echo -e "d\n\nn\n\n\n\n\nw" | fdisk $dev &>/dev/null - partprobe $dev - resize2fs ${dev}p2 - fi - ;; -pkgstatus ) - id=${args[1]} - pkg=$id - service=$id - case $id in - camilladsp ) - fileconf=$dircamilladsp/configs/camilladsp.yml - ;; - hostapd ) - conf="\ -# cat /etc/hostapd/hostapd.conf -$( cat /etc/hostapd/hostapd.conf ) - -# cat /etc/dnsmasq.conf" - ;; - localbrowser ) - pkg=chromium - fileconf=$dirsystem/localbrowser.conf - ;; - nfs-server ) - pkg=nfs-utils - systemctl -q is-active nfs-server && fileconf=/etc/exports - ;; - rtsp-simple-server ) - conf="\ -# rtl_test -t -$( script -c "timeout 1 rtl_test -t" | grep -v ^Script )" - ;; - smb ) - pkg=samba - fileconf=/etc/samba/smb.conf - ;; - snapclient|snapserver ) - pkg=snapcast - [[ $id == snapclient ]] && fileconf=/etc/default/snapclient - ;; - * ) - fileconf=/etc/$id.conf - ;; - esac - config="$( pacman -Q $pkg )" - if [[ $conf ]]; then - config+=" -$conf" - elif [[ -e $fileconf ]]; then - config+=" -# cat $fileconf -$( grep -v ^# $fileconf )" - fi - status=$( systemctl status $service \ - | sed -E '1 s|^.* (.*service) |\1|' \ - | sed -E '/^\s*Active:/ s|( active \(.*\))|\1|; s|( inactive \(.*\))|\1|; s|(failed)|\1|ig' ) - if [[ $pkg == chromium ]]; then - status=$( echo "$status" | grep -E -v 'Could not resolve keysym|Address family not supported by protocol|ERROR:chrome_browser_main_extra_parts_metrics' ) - elif [[ $pkg == nfs-utils ]]; then - status=$( echo "$status" | grep -v 'Protocol not supported' ) - fi - echo "\ -$config - -$status" - ;; playerstart ) player=${args[1]} [[ $player == bluetooth ]] && volumeGet save @@ -1175,15 +1074,6 @@ radiorestart ) sleep 1 rm $disshm/radiorestart ;; -rebootlist ) - [[ -e $dirshm/reboot ]] && cat $dirshm/reboot \ - | sort -u \ - | tr '\n' ^ \ - | head -c -1 - ;; -refreshbrowser ) - pushstream reload 1 - ;; relaystimerreset ) killall relays-timer.sh &> /dev/null $dirbash/settings/relays-timer.sh &> /dev/null & diff --git a/srv/http/bash/settings/system-datareset.sh b/srv/http/bash/settings/system-datareset.sh index 006301450..e1db5ec26 100644 --- a/srv/http/bash/settings/system-datareset.sh +++ b/srv/http/bash/settings/system-datareset.sh @@ -160,7 +160,7 @@ usermod -a -G root http # add user http to group root to allow /dev/gpiomem acce systemctl -q disable --now bluetooth hostapd shairport-sync smb snapserver spotifyd upmpdcli # set ownership and permissions -$dirbash/cmd.sh dirpermissions +$dirbash/settings/system.sh dirpermissions [[ $version ]] && exit diff --git a/srv/http/bash/settings/system.sh b/srv/http/bash/settings/system.sh index f9d31f507..320412141 100644 --- a/srv/http/bash/settings/system.sh +++ b/srv/http/bash/settings/system.sh @@ -7,6 +7,20 @@ filemodule=/etc/modules-load.d/raspberrypi.conf # convert each line to each args readarray -t args <<< "$1" +dirPermissions() { + chmod 755 /srv /srv/http /srv/http/* /mnt /mnt/MPD /mnt/MPD/*/ + chown http:http /srv /srv/http /srv/http/* /mnt /mnt/MPD /mnt/MPD/*/ + chmod -R 755 /srv/http/{assets,bash,data,settings} + chown -R http:http /srv/http/{assets,bash,data,settings} + chown mpd:audio $dirmpd $dirmpd/mpd.db $dirplaylists 2> /dev/null + if [[ $( readlink $dirshareddata ) == $dirdata ]]; then + chmod 777 $filesharedip $dirshareddata/system/{display,order} + readarray -t dirs <<< $( showmount --no-headers -e localhost | awk 'NF{NF-=1};1' ) + for dir in "${dirs[@]}"; do + chmod 777 "$dir" + done + fi +} pushReboot() { pushRefresh pushstreamNotify "${1//\"/\\\"}" 'Reboot required.' system 5000 @@ -236,7 +250,7 @@ datarestore ) mv $dirdata/{webradiosimg,webradio/img} fi # temp 20220808 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - $dirbash/cmd.sh dirpermissions + dirPermissions [[ -e $dirsystem/color ]] && $dirbash/cmd.sh color uuid1=$( head -1 /etc/fstab | cut -d' ' -f1 ) uuid2=${uuid1:0:-1}2 @@ -278,6 +292,9 @@ datarestore ) grep -q /mnt/MPD/SD /etc/exports && $dirbash/settings/features.sh nfsserver$'\n'true $dirbash/cmd.sh power$'\n'reboot ;; +dirpermissions ) + dirPermissions + ;; hddinfo ) dev=${args[1]} echo -n "\ @@ -556,6 +573,11 @@ mpdoleddisable ) $dirbash/settings/player-conf.sh pushRefresh ;; +mpdoledlogo ) + systemctl stop mpd_oled + type=$( grep mpd_oled /etc/systemd/system/mpd_oled.service | cut -d' ' -f3 ) + mpd_oled -o $type -L + ;; mpdoledset ) chip=${args[1]} baud=${args[2]} @@ -606,6 +628,68 @@ $description fi grep -B1 -A2 --no-group-separator "^${args[1],}" $filepackages ;; +pkgstatus ) + id=${args[1]} + pkg=$id + service=$id + case $id in + camilladsp ) + fileconf=$dircamilladsp/configs/camilladsp.yml + ;; + hostapd ) + conf="\ +# cat /etc/hostapd/hostapd.conf +$( cat /etc/hostapd/hostapd.conf ) + +# cat /etc/dnsmasq.conf" + ;; + localbrowser ) + pkg=chromium + fileconf=$dirsystem/localbrowser.conf + ;; + nfs-server ) + pkg=nfs-utils + systemctl -q is-active nfs-server && fileconf=/etc/exports + ;; + rtsp-simple-server ) + conf="\ +# rtl_test -t +$( script -c "timeout 1 rtl_test -t" | grep -v ^Script )" + ;; + smb ) + pkg=samba + fileconf=/etc/samba/smb.conf + ;; + snapclient|snapserver ) + pkg=snapcast + [[ $id == snapclient ]] && fileconf=/etc/default/snapclient + ;; + * ) + fileconf=/etc/$id.conf + ;; + esac + config="$( pacman -Q $pkg )" + if [[ $conf ]]; then + config+=" +$conf" + elif [[ -e $fileconf ]]; then + config+=" +# cat $fileconf +$( grep -v ^# $fileconf )" + fi + status=$( systemctl status $service \ + | sed -E '1 s|^.* (.*service) |\1|' \ + | sed -E '/^\s*Active:/ s|( active \(.*\))|\1|; s|( inactive \(.*\))|\1|; s|(failed)|\1|ig' ) + if [[ $pkg == chromium ]]; then + status=$( echo "$status" | grep -E -v 'Could not resolve keysym|Address family not supported by protocol|ERROR:chrome_browser_main_extra_parts_metrics' ) + elif [[ $pkg == nfs-utils ]]; then + status=$( echo "$status" | grep -v 'Protocol not supported' ) + fi + echo "\ +$config + +$status" + ;; powerbuttondisable ) if [[ -e $dirsystem/audiophonics ]]; then rm $dirsystem/audiophonics @@ -645,6 +729,9 @@ reserved=$reserved" > $dirsystem/powerbutton.conf [[ $reserved != $prevreserved ]] && pushReboot 'Power Button' fi ;; +rebootlist ) + [[ -e $dirshm/reboot ]] && cat $dirshm/reboot | sort -u + ;; relaysdisable ) rm -f $dirsystem/relays pushRefresh diff --git a/srv/http/bash/startup.sh b/srv/http/bash/startup.sh index ad81c8043..9305d6c21 100644 --- a/srv/http/bash/startup.sh +++ b/srv/http/bash/startup.sh @@ -107,7 +107,7 @@ if [[ -e $dirsystem/lcdchar ]]; then $dirbash/lcdcharinit.py $dirbash/lcdchar.py logo fi -[[ -e $dirsystem/mpdoled ]] && $dirbash/cmd.sh mpdoledlogo +[[ -e $dirsystem/mpdoled ]] && $dirbash/settings/system.sh mpdoledlogo [[ -e $dirsystem/soundprofile ]] && $dirbash/settings/system.sh soundprofile From 1d17da31568d827a8c977b2e2852868bcade6567 Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 15:49:05 +0700 Subject: [PATCH 18/36] Add files via upload --- context.js | 818 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 818 insertions(+) create mode 100644 context.js diff --git a/context.js b/context.js new file mode 100644 index 000000000..800920307 --- /dev/null +++ b/context.js @@ -0,0 +1,818 @@ +function addReplace( cmd, command, title, msg ) { + var play = cmd === 'addplay' || cmd === 'replaceplay'; + if ( play || cmd === 'replace' ) $( '#stop' ).click(); + bash( command, function() { + if ( G.display.playbackswitch && play ) $( '#playback' ).click(); + } ); + banner( title, msg, 'playlist' ); +} +function bookmarkNew() { + // #1 - track list - show image from licover + // #2 - dir list - show image from server + // #3 - no cover - icon + directory name + var path = G.list.path; + if ( path.slice( -4 ) === '.cue' ) path = getDirectory( path ); + if ( G.mode.slice( -5 ) === 'radio' ) path = G.mode +'/'+ path; + var bkpath = path.slice( 3, 8 ) === 'radio' ? '/srv/http/data/'+ path : '/mnt/MPD/'+ path; + bash( [ 'coverartget', bkpath ], function( coverart ) { + var icon = coverart ? '' : ''; + info( { + icon : 'bookmark' + , title : 'Add Bookmark' + , message : icon + +'
'+ path +'' + , textlabel : 'As:' + , focus : 0 + , values : path.split( '/' ).pop() + , checkblank : coverart ? '' : 1 + , beforeshow : function() { + $( '#infoContent input' ).parents( 'tr' ).toggleClass( 'hide', coverart !== '' ); + } + , ok : function() { + var name = infoVal(); + bash( [ 'bookmarkadd', name, path, coverart ], function( std ) { + if ( std == -1 ) { + bannerHide(); + info( { + icon : 'bookmark' + , title : 'Add Bookmark' + , message : icon + +'
'+ path +'' + +'

'+ name +' already exists.' + } ); + } else { + banner( 'Bookmark Added', path, 'bookmark' ); + } + } ); + } + } ); + } ); +} +function infoReplace( callback ) { + info( { + icon : 'playlist' + , title : 'Playlist Replace' + , message : 'Replace current playlist?' + , ok : callback + } ); +} +function playlistDelete() { + info( { + icon : 'file-playlist' + , title : 'Delete Playlist' + , message : 'Delete?' + +'
'+ G.list.name +'' + , oklabel : 'Delete' + , okcolor : red + , ok : function() { + bash( [ 'savedpldelete', G.list.name ] ); + } + } ); +} +function playlistLoad( path, play, replace ) { + G.local = 1; + banner( 'Saved Playlist', 'Load ...', 'file-playlist blink', -1 ); + list( { + cmd : 'load' + , name : path + , play : play + , replace : replace + }, function( data ) { + G.local = 0; + G.status.pllength = +data; + G.savedlist = 0; + banner( ( replace ? 'Playlist Replaced' : 'Playlist Added' ), 'Done', 'playlist' ); + } ); +} +function playlistNew( name ) { + info( { + icon : 'file-playlist' + , title : 'Save Playlist' + , message : 'Save current playlist as:' + , textlabel : 'Name' + , focus : 0 + , values : name + , checkblank : 1 + , ok : function() { + playlistSave( infoVal() ); + } + } ); +} +function playlistRename() { + var name = G.list.name; + info( { + icon : 'file-playlist' + , title : 'Rename Playlist' + , message : 'From: '+ name +'' + , textlabel : 'To' + , focus : 0 + , values : name + , checkchanged : 1 + , checkblank : 1 + , oklabel : 'Rename' + , ok : function() { + var newname = infoVal(); + playlistSave( newname, name ); + } + } ); +} +function playlistSave( name, oldname, replace ) { + if ( oldname ) { + bash( [ 'savedplrename', oldname, name, replace ], function( data ) { + if ( data == -1 ) playlistSaveExist( 'rename', name, oldname ); + } ); + } else { + bash( [ 'savedplsave', name, replace ], function( data ) { + if ( data == -1 ) { + playlistSaveExist( 'save', name ); + } else { + banner( 'Playlist Saved', name, 'playlist' ); + } + } ); + } +} +function playlistSaveExist( type, name, oldname ) { + var rename = type === 'rename'; + info( { + icon : 'file-playlist' + , title : rename ? 'Rename Playlist' : 'Save Playlist' + , message : 'Playlist: '+ name +'' + +'
Already exists.' + , buttonlabel : 'Rename' + , buttoncolor : orange + , button : function() { + setTimeout( function() { // fix error on repeating + rename ? playlistRename() : playlistNew( name ); + }, 0 ); + } + , oklabel : 'Replace' + , ok : function() { + rename ? playlistSave( name, oldname, 'replace' ) : playlistSave( name, '' , 'replace' ); + } + } ); +} +function addSimilar() { + banner( 'Playlist - Add Similar', 'Fetch similar list ...', 'lastfm blink', -1 ); + var url = 'http://ws.audioscrobbler.com/2.0/?method=track.getsimilar' + +'&artist='+ encodeURI( G.list.artist ) + +'&track='+ encodeURI( G.list.name ) + +'&api_key='+ G.apikeylastfm + +'&format=json' + +'&autocorrect=1'; + $.post( url, function( data ) { + var title = 'Playlist - Add Similar'; + if ( 'error' in data || !data.similartracks.track.length ) { + banner( title, 'Track not found.', 'lastfm' ); + } else { + var val = data.similartracks.track; + var iL = val.length; + var similar = ''; + for ( i = 0; i < iL; i++ ) { + similar += val[ i ].artist.name +'\n'+ val[ i ].name +'\n'; + } + banner( title, 'Find similar tracks from Library ...', 'library blink', -1 ); + bash( [ 'mpcsimilar', similar ], function( count ) { + getPlaylist(); + setButtonControl(); + banner( title, count +' tracks added.', 'library' ); + } ); + } + }, 'json' ); +} +function tagEditor() { + var name = [ 'Album', 'AlbumArtist', 'Artist', 'Composer', 'Conductor', 'Genre', 'Date', 'Title', 'Track' ]; + var format = name.map( el => el.toLowerCase() ); + if ( G.playlist ) { + var query = { + cmd : 'track' + , track : G.list.index + } + } else { + var file = G.list.path; + var fL = format.length; + if ( G.list.licover ) format = format.slice( 0, -2 ); + var query = { + query : 'track' + , file : file + , format : format + } + } + list( query, function( values ) { + if ( G.playlist ) { + v = values[ 0 ]; + file = v.file; + cue = 'Range' in v; + if ( cue ) file = file.replace( /\.[^/.]+$/, '.cue' ); + values = []; + name.forEach( function( k ) { + values.push( v[ k ] || '' ); + } ); + } else { + cue = file.includes( '.cue/track' ); + } + var parts = file.split( '/' ); + var filename = parts.pop(); + var filepath = parts.join( '/' ); + if ( file.includes( '.cue/track' ) ) { + file = filepath; + parts = file.split( '/' ); + filename = parts.pop(); + filepath = parts.join( '/' ); + } + name[ 1 ] = 'Album Artist'; + var label = []; + format.forEach( function( el, i ) { + if ( G.playlist && !values[ i ] ) { + delete values[ i ]; + return + } + + label.push( ''+ name[ i ] +' ' ); + } ); + if ( G.library ) { + var $img = $( '.licover' ).length ? $( '.licoverimg img' ) : G.list.li.find( 'img' ); + var src = $img.length ? $img.attr( 'src' ) : G.coverdefault; + } else { + var $img = G.list.li.find( 'img' ); + var src = $img.length ? $img.attr( 'src' ).replace( '/thumb.', '/coverart.' ) : G.coverdefault; + values = values.filter( val => val ); // reindex after deleting blank elements + } + var fileicon = file.slice( -4 ) !== '.cue' ? 'file-music' : 'file-playlist'; + var message = ''+ file +'' + +'
'; + if ( G.list.licover ) { + message += ''+ file; + } else { + message += ''+ filepath +'
'+ filename; + } + message += '
'; + var footer = ''; + footer += '
 Label
'; + if ( G.list.licover ) footer += '
*  Various values in tracks
'; + info( { + icon : G.playlist ? 'info-circle' : 'tag' + , title : G.playlist ? 'Track Info' : 'Tag Editor' + , width : 500 + , message : message + , messagealign : 'left' + , footer : footer + , footeralign : 'left' + , textlabel : label + , boxwidth : 'max' + , values : values + , checkchanged : 1 + , beforeshow : function() { + $( '#infoContent .infomessage' ).addClass( 'tagmessage' ); + $( '#infoContent .infofooter' ).addClass( 'tagfooter' ); + $( '#infoContent td i' ).css( 'cursor', 'pointer' ); + if ( G.playlist ) $( '#infoContent input' ).prop( 'disabled', 1 ); + var tableW = $( '#infoContent table' ).width(); + $( '#infoContent' ).on( 'click', '#taglabel', function() { + if ( $( '.taglabel' ).hasClass( 'hide' ) ) { + $( '.taglabel' ).removeClass( 'hide' ); + $( '#infoContent table' ).width( tableW ); + } else { + $( '.taglabel' ).addClass( 'hide' ); + } + } ).on( 'click', 'table i', function() { + var $this = $( this ); + var mode = $this.data( 'mode' ); + if ( [ 'title', 'track' ].includes( mode ) ) return + + var string = $this.parent().next().find( 'input' ).val(); + if ( !string ) return + + var query = { + query : 'find' + , mode : mode + , string : string + , format : [ 'album', 'artist' ] + } + list( query, function( html ) { + var data = { + html : html + , modetitle : string + , path : string + } + G.mode = mode; + renderLibraryList( data ); + query.gmode = mode; + query.modetitle = string; + tagModeSwitch(); + G.query.push( query ); + } ); + } ); + $( '.infomessage' ).click( function() { + if ( G.library ) return + + var query = { + query : 'ls' + , string : filepath + , format : [ 'file' ] + } + if ( filepath.slice( -4 ) === '.cue' ) filepath = getDirectory( filepath ); + list( query, function( html ) { + var data = { + html : html + , modetitle : filepath + , path : filepath + } + G.mode = filepath.split( '/' )[ 0 ].toLowerCase(); + tagModeSwitch(); + renderLibraryList( data ); + } ); + } ); + } + , okno : G.playlist + , ok : G.playlist ? '' : function() { + var tag = [ 'cmd-tageditor.sh', file, G.list.licover, cue ]; + var newvalues = infoVal(); + var val; + newvalues.forEach( function( v, i ) { + val = ( v === values[ i ] ) ? '' : ( v || -1 ); + tag.push( val ); + } ); + banner( 'Tag Editor', 'Change tags ...', 'tag blink', -1 ); + setTimeout( function() { + banner( 'Tag Editor', 'Update Library ...', 'tag blink' ); + }, 3000 ); + $.post( 'cmd.php', { cmd: 'sh', sh: tag } ); + if ( G.list.licover ) { + var tags = [ 'album', 'albumartist', 'artist', 'composer', 'conductor', 'genre', 'date' ]; + for ( i = 0; i < 7; i++ ) { + var v = newvalues[ i ]; + if ( v !== '*' ) $( '.li'+ tags[ i ] ).text( v ); + } + } else { + G.list.li.find( '.li1' ).text( newvalues[ 7 ] ); + G.list.li.find( '.track' ).text( newvalues[ 8 ] ); + } + } + } ); + }, 'json' ); +} +function tagModeSwitch() { + $( '#infoX' ).click(); + if ( G.playlist ) { + $( '#page-playlist' ).addClass( 'hide' ); + $( '#page-library' ).removeClass( 'hide' ); + G.playlist = 0; + G.library = 1; + G.page = 'library'; + } +} +function webRadioCoverart() { + if ( G.playback ) { + var coverart = G.status.stationcover || G.coverdefault; + var type = G.status.icon === 'dabradio' ? 'dabradio' : 'webradio'; + } else { + var src = G.list.li.find( '.lib-icon' ).attr( 'src' ); + var coverart = src ? src.replace( '-thumb.', '.' ) : G.coverdefault; + var type = G.mode; + } + var radioicon = coverart === G.coverdefault; + $( '#coverart, #liimg' ).removeAttr( 'style' ); + $( '.coveredit' ).remove(); + info( { + icon : '' + , title : ( type === 'webradio' ? 'Web' : 'DAB' ) +' Radio Cover Art' + , message : '' + +'

'+ ( G.library ? G.list.name : G.status.station ) +'

' + , filelabel : 'File' + , fileoklabel : 'Replace' + , filetype : 'image/*' + , buttonlabel : radioicon ? '' : 'Default' + , buttoncolor : radioicon ? '' : orange + , button : radioicon ? '' : function() { + bash( [ 'webradiocoverreset', coverart, type ] ); + } + , ok : function() { + if ( coverart !== G.coverdefault ) { + var imagefilenoext = coverart.slice( 0, -15 ); + } else { + if ( G.library ) { + var pathsplit = G.list.li.find( '.lipath' ).text().split( '//' ); + var url = pathsplit[ 0 ].replace( /.*\//, '' ) +'//'+ pathsplit[ 1 ]; + } else { + var url = G.status.file; + } + var imagefilenoext = '/data/'+ type +'/img/'+ url.replace( /\//g, '|' ); + } + imageReplace( '/srv/http'+ imagefilenoext, type ); + } + } ); +} +function webRadioDelete() { + var name = G.list.name; + var img = G.list.li.find( 'img' ).attr( 'src' ) || G.coverdefault; + var url = G.list.li.find( '.li2' ).text(); + info( { + icon : G.mode + , title : 'Delete '+ ( G.mode === 'webradio' ? 'Web Radio' : 'DAB Radio' ) + , width : 500 + , message : '
' + +'
'+ name +'' + +'
'+ url + , oklabel : 'Delete' + , okcolor : red + , ok : function() { + G.list.li.remove(); + var dir = $( '#lib-path .lipath' ).text(); + bash( ['webradiodelete', dir, url, G.mode ] ); + } + } ); +} +var htmlwebradio = `\ + + + + + + +
Name
URL
Charset +   +  New folder  +
+`; +function webRadioEdit() { + var name = G.list.name; + var img = G.list.li.find( 'img' ).attr( 'src' ) || G.coverdefault; + var pathsplit = G.list.path.split( '//' ); + var url = pathsplit[ 0 ].replace( /.*\//, '' ) +'//'+ pathsplit[ 1 ]; + var charset = G.list.li.data( 'charset' ); + info( { + icon : 'webradio' + , title : 'Edit Web Radio' + , content : htmlwebradio + , values : [ name, url, charset || 'UTF-8' ] + , checkchanged : 1 + , checkblank : [ 0, 1 ] + , boxwidth : 'max' + , beforeshow : function() { + $( '#addwebradiodir' ).remove(); + if ( url.includes( 'stream.radioparadise.com' ) || url.includes( 'icecast.radiofrance.fr' ) ) { + $( '#infoContent' ).find( 'tr:eq( 2 ), tr:eq( 3 )' ).remove(); + } + } + , oklabel : 'Save' + , ok : function() { + var dir = $( '#lib-path .lipath' ).text(); + var values = infoVal(); + var newname = values[ 0 ]; + var newurl = values[ 1 ]; + var newcharset = values[ 2 ].replace( /UTF-8|iso *-*/, '' ); + bash( [ 'webradioedit', dir, newname, newurl, newcharset, url ], function( error ) { + if ( error ) webRadioExists( error, '', newurl ); + } ); + } + } ); +} +function webRadioExists( error, name, url, charset ) { + var message = error == -1 ? 'already exists.' : 'contains no valid URL.'; + info( { + icon : 'webradio' + , title : 'Add Web Radio' + , message : ''+ url +'
'+ message + , ok : function() { + setTimeout( function() { + name ? webRadioNew( name, url, charset ) : webRadioEdit(); + }, 300 ); + } + } ); +} +function webRadioNew( name, url, charset ) { + info( { + icon : 'webradio' + , title : 'Add Web Radio' + , boxwidth : 'max' + , content : htmlwebradio + , focus : 0 + , values : name ? [ name, url, charset ] : [ '', '', 'UTF-8' ] + , checkblank : [ 0, 1 ] + , beforeshow : function() { + if ( $( '#lib-path .lipath' ).text() ) { + $( '#addwebradiodir' ).remove(); + } else { + $( '#addwebradiodir' ).click( function() { + info( { + icon : 'webradio' + , title : 'Add New Folder' + , textlabel : 'Name' + , focus : 0 + , checkblank : 1 + , ok : function() { + var dir = $( '#lib-path .lipath' ).text(); + bash( [ 'wrdirnew', dir, infoVal() ] ); + } + } ); + } ); + } + } + , ok : function() { + var values = infoVal(); + var name = values[ 0 ]; + var url = values[ 1 ]; + var charset = values[ 2 ].replace( /UTF-8|iso *-*/, '' ); + var dir = $( '#lib-path .lipath' ).text(); + bash( [ 'webradioadd', dir, name, url, charset ], function( error ) { + if ( error ) webRadioExists( error, name, url, charset ); + bannerHide(); + } ); + if ( [ 'm3u', 'pls' ].includes( url.slice( -3 ) ) ) banner( 'Web Radio', 'Add ...', 'webradio blink', -1 ); + } + } ); +} +function webRadioSave( url ) { + info( { + icon : 'webradio' + , title : 'Save Web Radio' + , message : url + , textlabel : 'Name' + , focus : 0 + , checkblank : 1 + , ok : function() { + G.local = 1; + var newname = infoVal().toString().replace( /\/\s*$/, '' ); // omit trailling / and space + bash( [ 'webradioadd', newname, url ], function() { + G.list.li.find( '.liname, .radioname' ).text( newname ); + G.list.li.find( '.li2 .radioname' ).append( ' • ' ); + G.list.li.find( '.savewr' ).remove(); + G.list.li.removeClass( 'notsaved' ); + G.local = 0; + } ); + } + } ); +} +//---------------------------------------------------------------------------------------------- +$( '.contextmenu a, .contextmenu .submenu' ).click( function() { + var $this = $( this ); + var cmd = $this.data( 'cmd' ); + menuHide(); + $( 'li.updn' ).removeClass( 'updn' ); + // playback ////////////////////////////////////////////////////////////// + if ( [ 'play', 'pause', 'stop' ].includes( cmd ) ) { + if ( cmd === 'play' ) { + if ( G.status.player !== 'mpd' ) { + $( '#stop' ).click(); + G.status.player = 'mpd'; + } + $( '#pl-list li' ).eq( G.list.li.index() ).click(); + } else { + $( '#'+ cmd ).click(); + } + return + } + + switch ( cmd ) { + case 'current': + bash( [ 'mpcsetcurrent', G.list.index + 1 ] ); + return + case 'directory': + if ( G.mode === 'latest' ) { + var path = getDirectory( G.list.path ); + var query = { + query : 'ls' + , string : path + , format : [ 'file' ] + } + var modetitle = path; + query.gmode = G.mode; + list( query, function( data ) { + G.mode = path.split( '/' )[ 0 ].toLowerCase(); + G.gmode = 'latest'; + data.path = path; + data.modetitle = modetitle; + renderLibraryList( data ); + }, 'json' ); + query.path = path; + query.modetitle = modetitle; + G.query.push( query ); + } else { + $( '#lib-list .liinfopath' ).click(); + } + return + case 'exclude': + info( { + icon : 'folder-forbid' + , title : 'Exclude Directory' + , message : 'Exclude from Library:' + +'
'+ G.list.path +'' + , ok : function() { + bash( [ 'ignoredir', G.list.path ], function() { + G.list.li.remove(); + } ); + var dir = G.list.path.split( '/' ).pop(); + } + } ); + return + case 'remove': + G.contextmenu = 1; + setTimeout( function() { G.contextmenu = 0 }, 500 ); + playlistRemove( G.list.li ); + return + case 'savedpladd': + if ( G.playlist ) { + var album = G.list.li.find( '.album' ).text(); + var file = G.list.path; + } else { + var album = $( '.licover .lialbum' ).text(); + var file = G.list.li.find( '.lipath' ).text(); + } + saveToPlaylist( G.list.name, album, file ); + return + case 'savedplremove': + local(); + var plname = $( '#pl-path .lipath' ).text(); + bash( [ 'savedpledit', plname, 'remove', G.list.li.index() + 1 ] ); + G.list.li.remove(); + return + case 'similar': + if ( G.display.plsimilar ) { + info( { + icon : 'lastfm' + , title : 'Add Similar' + , message : 'Search and add similar tracks from Library?' + , ok : function() { + addSimilar(); + } + } ); + } else { + addSimilar(); + } + return + case 'tag': + tagEditor(); + return + case 'thumb': + info( { + icon : '' + , title : 'Album Thumbnails' + , message : 'Update album thumbnails in:' + +'
'+ G.list.path +'' + , ok : function() { + thumbUpdate( G.list.path ); + } + } ); + return + case 'update': + if ( G.list.path.slice( -3 ) === 'cue' ) G.list.path = getDirectory( G.list.path ); + infoUpdate( G.list.path ); + return + case 'wrdirdelete': + var path = G.list.li.find( '.lipath' ).text(); + info( { + icon : G.mode + , title : 'Delete Folder' + , message : 'Folder:' + +'
'+ path +'' + , oklabel : 'Delete' + , okcolor : red + , ok : function() { + bash( [ 'wrdirdelete', path, G.mode ], function( std ) { + if ( std == -1 ) { + info( { + icon : 'webradio' + , title : 'Web Radio Delete' + , message : 'Folder not empty:' + +'
'+ path +'' + +'
Confirm delete?' + , oklabel : 'Delete' + , okcolor : red + , ok : function() { + bash( [ 'wrdirdelete', path, G.mode, 'noconfirm' ] ); + } + } ); + } + } ); + } + } ); + return + case 'wrdirrename': + var path = G.list.li.find( '.lipath' ).text().split( '/' ); + var name = path.pop(); + var path = path.join( '/' ); + info( { + icon : G.mode + , title : 'Rename Folder' + , textlabel : 'Name' + , focus : 0 + , values : name + , checkblank : 1 + , checkchange : 1 + , oklabel : 'Rename' + , ok : function() { + bash( [ 'wrdirrename', path, name, infoVal(), G.mode ] ); + } + } ); + return + case 'wrsave': + webRadioSave( G.list.li.find( '.lipath' ).text() ); + return + } + + // functions with dialogue box //////////////////////////////////////////// + var contextFunction = { + bookmark : bookmarkNew + , plrename : playlistRename + , pldelete : playlistDelete + , wrcoverart : webRadioCoverart + , wrdelete : webRadioDelete + , wredit : webRadioEdit + } + if ( cmd in contextFunction ) { + contextFunction[ cmd ](); + return + } + + // replaceplay|replace|addplay|add ////////////////////////////////////////// + var path = G.list.path; + if ( G.mode.slice( -5 ) === 'radio' ) { + var pathsplit = path.split( '//' ); + path = pathsplit[ 0 ].replace( /.*\//, '' ) +'//'+ pathsplit[ 1 ]; + } + var mpccmd; + // must keep order otherwise replaceplay -> play, addplay -> play + var mode = cmd.replace( /replaceplay|replace|addplay|add/, '' ); + switch ( mode ) { + case '': + if ( G.list.singletrack || G.mode.slice( -5 ) === 'radio' ) { // single track + mpccmd = [ 'mpcadd', path ]; + } else if ( $( '.licover' ).length && !$( '.licover .lipath' ).length ) { + mpccmd = [ 'mpcfindadd', 'multi', G.mode, path, 'album', G.list.album ]; + } else { // directory or album + mpccmd = [ 'mpcls', path ]; + } + break; + case 'pl': + cmd = cmd.slice( 2 ); + if ( G.library ) { + mpccmd = [ 'mpcload', path ]; + } else { // saved playlist + var play = cmd.slice( -1 ) === 'y' ? 1 : 0; + var replace = cmd.slice( 0, 1 ) === 'r' ? 1 : 0; + if ( replace && G.display.plclear && G.status.pllength ) { + infoReplace( function() { + playlistLoad( path, play, replace ); + } ); + } else { + playlistLoad( path, play, replace ); + } + return + } + break; + case 'playnext': + mpccmd = [ 'mpcaddplaynext', path ]; + break + case 'wr': + cmd = cmd.slice( 2 ); + var charset = G.list.li.data( 'charset' ); + if ( charset ) path += '#charset='+ charset + mpccmd = [ 'mpcadd', path ]; + break; + default: + if ( !G.list.name ) { + mpccmd = [ 'mpcfindadd', mode, path ]; + if ( G.list.artist ) mpccmd.push( 'artist', G.list.artist ); + } else { + mpccmd = [ 'mpcfindadd', 'multi', G.mode, $( '#mode-title' ).text(), 'album', G.list.name ]; + } + } + if ( !mpccmd ) mpccmd = []; + var sleep = G.mode.slice( -5 ) === 'radio' ? 1 : 0.2; + if ( G.status.state === 'play' && G.status.webradio ) sleep += 1; + var contextCommand = { + add : mpccmd + , playnext : mpccmd + , addplay : mpccmd.concat( [ 'addplay', sleep ] ) + , replace : mpccmd.concat( 'replace' ) + , replaceplay : mpccmd.concat( [ 'replaceplay', sleep ] ) + } + cmd = cmd.replace( /album|artist|composer|conductor|date|genre/g, '' ); + var command = contextCommand[ cmd ]; + if ( cmd === 'add' ) { + var title = 'Add to Playlist'; + } else if ( cmd === 'addplay' ) { + var title = 'Add to Playlist and play'; + } else if ( cmd === 'playnext' ) { + var title = 'Add to Playlist to play next'; + } else { + var title = 'Replace playlist'+ ( cmd === 'replace' ? '' : ' and play' ); + } + if ( G.list.li.hasClass( 'licover' ) ) { + var msg = G.list.li.find( '.lialbum' ).text() + +''+ G.list.li.find( '.liartist' ).text() +''; + } else if ( G.list.li.find( '.li1' ).length ) { + var msg = G.list.li.find( '.li1' )[ 0 ].outerHTML + + G.list.li.find( '.li2' )[ 0 ].outerHTML; + msg = msg.replace( '', '' ).replace( '', '' ); + } else { + var msg = G.list.li.find( '.lipath' ).text() || G.list.li.find( '.liname' ).text(); + } + if ( G.display.plclear && ( cmd === 'replace' || cmd === 'replaceplay' ) ) { + infoReplace( function() { + addReplace( cmd, command, title, msg ); + } ); + } else { + addReplace( cmd, command, title, msg ); + } +} ); From 0de7c917641fc9a508e4054d7180d8506c6005b3 Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 15:50:24 +0700 Subject: [PATCH 19/36] Add files via upload --- srv/http/assets/js/context.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/srv/http/assets/js/context.js b/srv/http/assets/js/context.js index 4a014458c..800920307 100644 --- a/srv/http/assets/js/context.js +++ b/srv/http/assets/js/context.js @@ -1,9 +1,8 @@ function addReplace( cmd, command, title, msg ) { - if ( cmd === 'addplay' || cmd === 'replaceplay' || cmd === 'replace' ) $( '#stop' ).click(); + var play = cmd === 'addplay' || cmd === 'replaceplay'; + if ( play || cmd === 'replace' ) $( '#stop' ).click(); bash( command, function() { - if ( !G.display.playbackswitch ) return - - if ( cmd === 'addplay' || cmd === 'replaceplay' ) $( '#playback' ).click(); + if ( G.display.playbackswitch && play ) $( '#playback' ).click(); } ); banner( title, msg, 'playlist' ); } @@ -780,6 +779,7 @@ $( '.contextmenu a, .contextmenu .submenu' ).click( function() { } if ( !mpccmd ) mpccmd = []; var sleep = G.mode.slice( -5 ) === 'radio' ? 1 : 0.2; + if ( G.status.state === 'play' && G.status.webradio ) sleep += 1; var contextCommand = { add : mpccmd , playnext : mpccmd From a6a9841ed610f1e1ae49f6dc2fc8df5e64ec8952 Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 15:50:40 +0700 Subject: [PATCH 20/36] Delete context.js --- context.js | 818 ----------------------------------------------------- 1 file changed, 818 deletions(-) delete mode 100644 context.js diff --git a/context.js b/context.js deleted file mode 100644 index 800920307..000000000 --- a/context.js +++ /dev/null @@ -1,818 +0,0 @@ -function addReplace( cmd, command, title, msg ) { - var play = cmd === 'addplay' || cmd === 'replaceplay'; - if ( play || cmd === 'replace' ) $( '#stop' ).click(); - bash( command, function() { - if ( G.display.playbackswitch && play ) $( '#playback' ).click(); - } ); - banner( title, msg, 'playlist' ); -} -function bookmarkNew() { - // #1 - track list - show image from licover - // #2 - dir list - show image from server - // #3 - no cover - icon + directory name - var path = G.list.path; - if ( path.slice( -4 ) === '.cue' ) path = getDirectory( path ); - if ( G.mode.slice( -5 ) === 'radio' ) path = G.mode +'/'+ path; - var bkpath = path.slice( 3, 8 ) === 'radio' ? '/srv/http/data/'+ path : '/mnt/MPD/'+ path; - bash( [ 'coverartget', bkpath ], function( coverart ) { - var icon = coverart ? '' : ''; - info( { - icon : 'bookmark' - , title : 'Add Bookmark' - , message : icon - +'
'+ path +'' - , textlabel : 'As:' - , focus : 0 - , values : path.split( '/' ).pop() - , checkblank : coverart ? '' : 1 - , beforeshow : function() { - $( '#infoContent input' ).parents( 'tr' ).toggleClass( 'hide', coverart !== '' ); - } - , ok : function() { - var name = infoVal(); - bash( [ 'bookmarkadd', name, path, coverart ], function( std ) { - if ( std == -1 ) { - bannerHide(); - info( { - icon : 'bookmark' - , title : 'Add Bookmark' - , message : icon - +'
'+ path +'' - +'

'+ name +' already exists.' - } ); - } else { - banner( 'Bookmark Added', path, 'bookmark' ); - } - } ); - } - } ); - } ); -} -function infoReplace( callback ) { - info( { - icon : 'playlist' - , title : 'Playlist Replace' - , message : 'Replace current playlist?' - , ok : callback - } ); -} -function playlistDelete() { - info( { - icon : 'file-playlist' - , title : 'Delete Playlist' - , message : 'Delete?' - +'
'+ G.list.name +'' - , oklabel : 'Delete' - , okcolor : red - , ok : function() { - bash( [ 'savedpldelete', G.list.name ] ); - } - } ); -} -function playlistLoad( path, play, replace ) { - G.local = 1; - banner( 'Saved Playlist', 'Load ...', 'file-playlist blink', -1 ); - list( { - cmd : 'load' - , name : path - , play : play - , replace : replace - }, function( data ) { - G.local = 0; - G.status.pllength = +data; - G.savedlist = 0; - banner( ( replace ? 'Playlist Replaced' : 'Playlist Added' ), 'Done', 'playlist' ); - } ); -} -function playlistNew( name ) { - info( { - icon : 'file-playlist' - , title : 'Save Playlist' - , message : 'Save current playlist as:' - , textlabel : 'Name' - , focus : 0 - , values : name - , checkblank : 1 - , ok : function() { - playlistSave( infoVal() ); - } - } ); -} -function playlistRename() { - var name = G.list.name; - info( { - icon : 'file-playlist' - , title : 'Rename Playlist' - , message : 'From: '+ name +'' - , textlabel : 'To' - , focus : 0 - , values : name - , checkchanged : 1 - , checkblank : 1 - , oklabel : 'Rename' - , ok : function() { - var newname = infoVal(); - playlistSave( newname, name ); - } - } ); -} -function playlistSave( name, oldname, replace ) { - if ( oldname ) { - bash( [ 'savedplrename', oldname, name, replace ], function( data ) { - if ( data == -1 ) playlistSaveExist( 'rename', name, oldname ); - } ); - } else { - bash( [ 'savedplsave', name, replace ], function( data ) { - if ( data == -1 ) { - playlistSaveExist( 'save', name ); - } else { - banner( 'Playlist Saved', name, 'playlist' ); - } - } ); - } -} -function playlistSaveExist( type, name, oldname ) { - var rename = type === 'rename'; - info( { - icon : 'file-playlist' - , title : rename ? 'Rename Playlist' : 'Save Playlist' - , message : 'Playlist: '+ name +'' - +'
Already exists.' - , buttonlabel : 'Rename' - , buttoncolor : orange - , button : function() { - setTimeout( function() { // fix error on repeating - rename ? playlistRename() : playlistNew( name ); - }, 0 ); - } - , oklabel : 'Replace' - , ok : function() { - rename ? playlistSave( name, oldname, 'replace' ) : playlistSave( name, '' , 'replace' ); - } - } ); -} -function addSimilar() { - banner( 'Playlist - Add Similar', 'Fetch similar list ...', 'lastfm blink', -1 ); - var url = 'http://ws.audioscrobbler.com/2.0/?method=track.getsimilar' - +'&artist='+ encodeURI( G.list.artist ) - +'&track='+ encodeURI( G.list.name ) - +'&api_key='+ G.apikeylastfm - +'&format=json' - +'&autocorrect=1'; - $.post( url, function( data ) { - var title = 'Playlist - Add Similar'; - if ( 'error' in data || !data.similartracks.track.length ) { - banner( title, 'Track not found.', 'lastfm' ); - } else { - var val = data.similartracks.track; - var iL = val.length; - var similar = ''; - for ( i = 0; i < iL; i++ ) { - similar += val[ i ].artist.name +'\n'+ val[ i ].name +'\n'; - } - banner( title, 'Find similar tracks from Library ...', 'library blink', -1 ); - bash( [ 'mpcsimilar', similar ], function( count ) { - getPlaylist(); - setButtonControl(); - banner( title, count +' tracks added.', 'library' ); - } ); - } - }, 'json' ); -} -function tagEditor() { - var name = [ 'Album', 'AlbumArtist', 'Artist', 'Composer', 'Conductor', 'Genre', 'Date', 'Title', 'Track' ]; - var format = name.map( el => el.toLowerCase() ); - if ( G.playlist ) { - var query = { - cmd : 'track' - , track : G.list.index - } - } else { - var file = G.list.path; - var fL = format.length; - if ( G.list.licover ) format = format.slice( 0, -2 ); - var query = { - query : 'track' - , file : file - , format : format - } - } - list( query, function( values ) { - if ( G.playlist ) { - v = values[ 0 ]; - file = v.file; - cue = 'Range' in v; - if ( cue ) file = file.replace( /\.[^/.]+$/, '.cue' ); - values = []; - name.forEach( function( k ) { - values.push( v[ k ] || '' ); - } ); - } else { - cue = file.includes( '.cue/track' ); - } - var parts = file.split( '/' ); - var filename = parts.pop(); - var filepath = parts.join( '/' ); - if ( file.includes( '.cue/track' ) ) { - file = filepath; - parts = file.split( '/' ); - filename = parts.pop(); - filepath = parts.join( '/' ); - } - name[ 1 ] = 'Album Artist'; - var label = []; - format.forEach( function( el, i ) { - if ( G.playlist && !values[ i ] ) { - delete values[ i ]; - return - } - - label.push( ''+ name[ i ] +' ' ); - } ); - if ( G.library ) { - var $img = $( '.licover' ).length ? $( '.licoverimg img' ) : G.list.li.find( 'img' ); - var src = $img.length ? $img.attr( 'src' ) : G.coverdefault; - } else { - var $img = G.list.li.find( 'img' ); - var src = $img.length ? $img.attr( 'src' ).replace( '/thumb.', '/coverart.' ) : G.coverdefault; - values = values.filter( val => val ); // reindex after deleting blank elements - } - var fileicon = file.slice( -4 ) !== '.cue' ? 'file-music' : 'file-playlist'; - var message = ''+ file +'' - +'
'; - if ( G.list.licover ) { - message += ''+ file; - } else { - message += ''+ filepath +'
'+ filename; - } - message += '
'; - var footer = ''; - footer += '
 Label
'; - if ( G.list.licover ) footer += '
*  Various values in tracks
'; - info( { - icon : G.playlist ? 'info-circle' : 'tag' - , title : G.playlist ? 'Track Info' : 'Tag Editor' - , width : 500 - , message : message - , messagealign : 'left' - , footer : footer - , footeralign : 'left' - , textlabel : label - , boxwidth : 'max' - , values : values - , checkchanged : 1 - , beforeshow : function() { - $( '#infoContent .infomessage' ).addClass( 'tagmessage' ); - $( '#infoContent .infofooter' ).addClass( 'tagfooter' ); - $( '#infoContent td i' ).css( 'cursor', 'pointer' ); - if ( G.playlist ) $( '#infoContent input' ).prop( 'disabled', 1 ); - var tableW = $( '#infoContent table' ).width(); - $( '#infoContent' ).on( 'click', '#taglabel', function() { - if ( $( '.taglabel' ).hasClass( 'hide' ) ) { - $( '.taglabel' ).removeClass( 'hide' ); - $( '#infoContent table' ).width( tableW ); - } else { - $( '.taglabel' ).addClass( 'hide' ); - } - } ).on( 'click', 'table i', function() { - var $this = $( this ); - var mode = $this.data( 'mode' ); - if ( [ 'title', 'track' ].includes( mode ) ) return - - var string = $this.parent().next().find( 'input' ).val(); - if ( !string ) return - - var query = { - query : 'find' - , mode : mode - , string : string - , format : [ 'album', 'artist' ] - } - list( query, function( html ) { - var data = { - html : html - , modetitle : string - , path : string - } - G.mode = mode; - renderLibraryList( data ); - query.gmode = mode; - query.modetitle = string; - tagModeSwitch(); - G.query.push( query ); - } ); - } ); - $( '.infomessage' ).click( function() { - if ( G.library ) return - - var query = { - query : 'ls' - , string : filepath - , format : [ 'file' ] - } - if ( filepath.slice( -4 ) === '.cue' ) filepath = getDirectory( filepath ); - list( query, function( html ) { - var data = { - html : html - , modetitle : filepath - , path : filepath - } - G.mode = filepath.split( '/' )[ 0 ].toLowerCase(); - tagModeSwitch(); - renderLibraryList( data ); - } ); - } ); - } - , okno : G.playlist - , ok : G.playlist ? '' : function() { - var tag = [ 'cmd-tageditor.sh', file, G.list.licover, cue ]; - var newvalues = infoVal(); - var val; - newvalues.forEach( function( v, i ) { - val = ( v === values[ i ] ) ? '' : ( v || -1 ); - tag.push( val ); - } ); - banner( 'Tag Editor', 'Change tags ...', 'tag blink', -1 ); - setTimeout( function() { - banner( 'Tag Editor', 'Update Library ...', 'tag blink' ); - }, 3000 ); - $.post( 'cmd.php', { cmd: 'sh', sh: tag } ); - if ( G.list.licover ) { - var tags = [ 'album', 'albumartist', 'artist', 'composer', 'conductor', 'genre', 'date' ]; - for ( i = 0; i < 7; i++ ) { - var v = newvalues[ i ]; - if ( v !== '*' ) $( '.li'+ tags[ i ] ).text( v ); - } - } else { - G.list.li.find( '.li1' ).text( newvalues[ 7 ] ); - G.list.li.find( '.track' ).text( newvalues[ 8 ] ); - } - } - } ); - }, 'json' ); -} -function tagModeSwitch() { - $( '#infoX' ).click(); - if ( G.playlist ) { - $( '#page-playlist' ).addClass( 'hide' ); - $( '#page-library' ).removeClass( 'hide' ); - G.playlist = 0; - G.library = 1; - G.page = 'library'; - } -} -function webRadioCoverart() { - if ( G.playback ) { - var coverart = G.status.stationcover || G.coverdefault; - var type = G.status.icon === 'dabradio' ? 'dabradio' : 'webradio'; - } else { - var src = G.list.li.find( '.lib-icon' ).attr( 'src' ); - var coverart = src ? src.replace( '-thumb.', '.' ) : G.coverdefault; - var type = G.mode; - } - var radioicon = coverart === G.coverdefault; - $( '#coverart, #liimg' ).removeAttr( 'style' ); - $( '.coveredit' ).remove(); - info( { - icon : '' - , title : ( type === 'webradio' ? 'Web' : 'DAB' ) +' Radio Cover Art' - , message : '' - +'

'+ ( G.library ? G.list.name : G.status.station ) +'

' - , filelabel : 'File' - , fileoklabel : 'Replace' - , filetype : 'image/*' - , buttonlabel : radioicon ? '' : 'Default' - , buttoncolor : radioicon ? '' : orange - , button : radioicon ? '' : function() { - bash( [ 'webradiocoverreset', coverart, type ] ); - } - , ok : function() { - if ( coverart !== G.coverdefault ) { - var imagefilenoext = coverart.slice( 0, -15 ); - } else { - if ( G.library ) { - var pathsplit = G.list.li.find( '.lipath' ).text().split( '//' ); - var url = pathsplit[ 0 ].replace( /.*\//, '' ) +'//'+ pathsplit[ 1 ]; - } else { - var url = G.status.file; - } - var imagefilenoext = '/data/'+ type +'/img/'+ url.replace( /\//g, '|' ); - } - imageReplace( '/srv/http'+ imagefilenoext, type ); - } - } ); -} -function webRadioDelete() { - var name = G.list.name; - var img = G.list.li.find( 'img' ).attr( 'src' ) || G.coverdefault; - var url = G.list.li.find( '.li2' ).text(); - info( { - icon : G.mode - , title : 'Delete '+ ( G.mode === 'webradio' ? 'Web Radio' : 'DAB Radio' ) - , width : 500 - , message : '
' - +'
'+ name +'' - +'
'+ url - , oklabel : 'Delete' - , okcolor : red - , ok : function() { - G.list.li.remove(); - var dir = $( '#lib-path .lipath' ).text(); - bash( ['webradiodelete', dir, url, G.mode ] ); - } - } ); -} -var htmlwebradio = `\ - - - - - - -
Name
URL
Charset -   -  New folder  -
-`; -function webRadioEdit() { - var name = G.list.name; - var img = G.list.li.find( 'img' ).attr( 'src' ) || G.coverdefault; - var pathsplit = G.list.path.split( '//' ); - var url = pathsplit[ 0 ].replace( /.*\//, '' ) +'//'+ pathsplit[ 1 ]; - var charset = G.list.li.data( 'charset' ); - info( { - icon : 'webradio' - , title : 'Edit Web Radio' - , content : htmlwebradio - , values : [ name, url, charset || 'UTF-8' ] - , checkchanged : 1 - , checkblank : [ 0, 1 ] - , boxwidth : 'max' - , beforeshow : function() { - $( '#addwebradiodir' ).remove(); - if ( url.includes( 'stream.radioparadise.com' ) || url.includes( 'icecast.radiofrance.fr' ) ) { - $( '#infoContent' ).find( 'tr:eq( 2 ), tr:eq( 3 )' ).remove(); - } - } - , oklabel : 'Save' - , ok : function() { - var dir = $( '#lib-path .lipath' ).text(); - var values = infoVal(); - var newname = values[ 0 ]; - var newurl = values[ 1 ]; - var newcharset = values[ 2 ].replace( /UTF-8|iso *-*/, '' ); - bash( [ 'webradioedit', dir, newname, newurl, newcharset, url ], function( error ) { - if ( error ) webRadioExists( error, '', newurl ); - } ); - } - } ); -} -function webRadioExists( error, name, url, charset ) { - var message = error == -1 ? 'already exists.' : 'contains no valid URL.'; - info( { - icon : 'webradio' - , title : 'Add Web Radio' - , message : ''+ url +'
'+ message - , ok : function() { - setTimeout( function() { - name ? webRadioNew( name, url, charset ) : webRadioEdit(); - }, 300 ); - } - } ); -} -function webRadioNew( name, url, charset ) { - info( { - icon : 'webradio' - , title : 'Add Web Radio' - , boxwidth : 'max' - , content : htmlwebradio - , focus : 0 - , values : name ? [ name, url, charset ] : [ '', '', 'UTF-8' ] - , checkblank : [ 0, 1 ] - , beforeshow : function() { - if ( $( '#lib-path .lipath' ).text() ) { - $( '#addwebradiodir' ).remove(); - } else { - $( '#addwebradiodir' ).click( function() { - info( { - icon : 'webradio' - , title : 'Add New Folder' - , textlabel : 'Name' - , focus : 0 - , checkblank : 1 - , ok : function() { - var dir = $( '#lib-path .lipath' ).text(); - bash( [ 'wrdirnew', dir, infoVal() ] ); - } - } ); - } ); - } - } - , ok : function() { - var values = infoVal(); - var name = values[ 0 ]; - var url = values[ 1 ]; - var charset = values[ 2 ].replace( /UTF-8|iso *-*/, '' ); - var dir = $( '#lib-path .lipath' ).text(); - bash( [ 'webradioadd', dir, name, url, charset ], function( error ) { - if ( error ) webRadioExists( error, name, url, charset ); - bannerHide(); - } ); - if ( [ 'm3u', 'pls' ].includes( url.slice( -3 ) ) ) banner( 'Web Radio', 'Add ...', 'webradio blink', -1 ); - } - } ); -} -function webRadioSave( url ) { - info( { - icon : 'webradio' - , title : 'Save Web Radio' - , message : url - , textlabel : 'Name' - , focus : 0 - , checkblank : 1 - , ok : function() { - G.local = 1; - var newname = infoVal().toString().replace( /\/\s*$/, '' ); // omit trailling / and space - bash( [ 'webradioadd', newname, url ], function() { - G.list.li.find( '.liname, .radioname' ).text( newname ); - G.list.li.find( '.li2 .radioname' ).append( ' • ' ); - G.list.li.find( '.savewr' ).remove(); - G.list.li.removeClass( 'notsaved' ); - G.local = 0; - } ); - } - } ); -} -//---------------------------------------------------------------------------------------------- -$( '.contextmenu a, .contextmenu .submenu' ).click( function() { - var $this = $( this ); - var cmd = $this.data( 'cmd' ); - menuHide(); - $( 'li.updn' ).removeClass( 'updn' ); - // playback ////////////////////////////////////////////////////////////// - if ( [ 'play', 'pause', 'stop' ].includes( cmd ) ) { - if ( cmd === 'play' ) { - if ( G.status.player !== 'mpd' ) { - $( '#stop' ).click(); - G.status.player = 'mpd'; - } - $( '#pl-list li' ).eq( G.list.li.index() ).click(); - } else { - $( '#'+ cmd ).click(); - } - return - } - - switch ( cmd ) { - case 'current': - bash( [ 'mpcsetcurrent', G.list.index + 1 ] ); - return - case 'directory': - if ( G.mode === 'latest' ) { - var path = getDirectory( G.list.path ); - var query = { - query : 'ls' - , string : path - , format : [ 'file' ] - } - var modetitle = path; - query.gmode = G.mode; - list( query, function( data ) { - G.mode = path.split( '/' )[ 0 ].toLowerCase(); - G.gmode = 'latest'; - data.path = path; - data.modetitle = modetitle; - renderLibraryList( data ); - }, 'json' ); - query.path = path; - query.modetitle = modetitle; - G.query.push( query ); - } else { - $( '#lib-list .liinfopath' ).click(); - } - return - case 'exclude': - info( { - icon : 'folder-forbid' - , title : 'Exclude Directory' - , message : 'Exclude from Library:' - +'
'+ G.list.path +'' - , ok : function() { - bash( [ 'ignoredir', G.list.path ], function() { - G.list.li.remove(); - } ); - var dir = G.list.path.split( '/' ).pop(); - } - } ); - return - case 'remove': - G.contextmenu = 1; - setTimeout( function() { G.contextmenu = 0 }, 500 ); - playlistRemove( G.list.li ); - return - case 'savedpladd': - if ( G.playlist ) { - var album = G.list.li.find( '.album' ).text(); - var file = G.list.path; - } else { - var album = $( '.licover .lialbum' ).text(); - var file = G.list.li.find( '.lipath' ).text(); - } - saveToPlaylist( G.list.name, album, file ); - return - case 'savedplremove': - local(); - var plname = $( '#pl-path .lipath' ).text(); - bash( [ 'savedpledit', plname, 'remove', G.list.li.index() + 1 ] ); - G.list.li.remove(); - return - case 'similar': - if ( G.display.plsimilar ) { - info( { - icon : 'lastfm' - , title : 'Add Similar' - , message : 'Search and add similar tracks from Library?' - , ok : function() { - addSimilar(); - } - } ); - } else { - addSimilar(); - } - return - case 'tag': - tagEditor(); - return - case 'thumb': - info( { - icon : '' - , title : 'Album Thumbnails' - , message : 'Update album thumbnails in:' - +'
'+ G.list.path +'' - , ok : function() { - thumbUpdate( G.list.path ); - } - } ); - return - case 'update': - if ( G.list.path.slice( -3 ) === 'cue' ) G.list.path = getDirectory( G.list.path ); - infoUpdate( G.list.path ); - return - case 'wrdirdelete': - var path = G.list.li.find( '.lipath' ).text(); - info( { - icon : G.mode - , title : 'Delete Folder' - , message : 'Folder:' - +'
'+ path +'' - , oklabel : 'Delete' - , okcolor : red - , ok : function() { - bash( [ 'wrdirdelete', path, G.mode ], function( std ) { - if ( std == -1 ) { - info( { - icon : 'webradio' - , title : 'Web Radio Delete' - , message : 'Folder not empty:' - +'
'+ path +'' - +'
Confirm delete?' - , oklabel : 'Delete' - , okcolor : red - , ok : function() { - bash( [ 'wrdirdelete', path, G.mode, 'noconfirm' ] ); - } - } ); - } - } ); - } - } ); - return - case 'wrdirrename': - var path = G.list.li.find( '.lipath' ).text().split( '/' ); - var name = path.pop(); - var path = path.join( '/' ); - info( { - icon : G.mode - , title : 'Rename Folder' - , textlabel : 'Name' - , focus : 0 - , values : name - , checkblank : 1 - , checkchange : 1 - , oklabel : 'Rename' - , ok : function() { - bash( [ 'wrdirrename', path, name, infoVal(), G.mode ] ); - } - } ); - return - case 'wrsave': - webRadioSave( G.list.li.find( '.lipath' ).text() ); - return - } - - // functions with dialogue box //////////////////////////////////////////// - var contextFunction = { - bookmark : bookmarkNew - , plrename : playlistRename - , pldelete : playlistDelete - , wrcoverart : webRadioCoverart - , wrdelete : webRadioDelete - , wredit : webRadioEdit - } - if ( cmd in contextFunction ) { - contextFunction[ cmd ](); - return - } - - // replaceplay|replace|addplay|add ////////////////////////////////////////// - var path = G.list.path; - if ( G.mode.slice( -5 ) === 'radio' ) { - var pathsplit = path.split( '//' ); - path = pathsplit[ 0 ].replace( /.*\//, '' ) +'//'+ pathsplit[ 1 ]; - } - var mpccmd; - // must keep order otherwise replaceplay -> play, addplay -> play - var mode = cmd.replace( /replaceplay|replace|addplay|add/, '' ); - switch ( mode ) { - case '': - if ( G.list.singletrack || G.mode.slice( -5 ) === 'radio' ) { // single track - mpccmd = [ 'mpcadd', path ]; - } else if ( $( '.licover' ).length && !$( '.licover .lipath' ).length ) { - mpccmd = [ 'mpcfindadd', 'multi', G.mode, path, 'album', G.list.album ]; - } else { // directory or album - mpccmd = [ 'mpcls', path ]; - } - break; - case 'pl': - cmd = cmd.slice( 2 ); - if ( G.library ) { - mpccmd = [ 'mpcload', path ]; - } else { // saved playlist - var play = cmd.slice( -1 ) === 'y' ? 1 : 0; - var replace = cmd.slice( 0, 1 ) === 'r' ? 1 : 0; - if ( replace && G.display.plclear && G.status.pllength ) { - infoReplace( function() { - playlistLoad( path, play, replace ); - } ); - } else { - playlistLoad( path, play, replace ); - } - return - } - break; - case 'playnext': - mpccmd = [ 'mpcaddplaynext', path ]; - break - case 'wr': - cmd = cmd.slice( 2 ); - var charset = G.list.li.data( 'charset' ); - if ( charset ) path += '#charset='+ charset - mpccmd = [ 'mpcadd', path ]; - break; - default: - if ( !G.list.name ) { - mpccmd = [ 'mpcfindadd', mode, path ]; - if ( G.list.artist ) mpccmd.push( 'artist', G.list.artist ); - } else { - mpccmd = [ 'mpcfindadd', 'multi', G.mode, $( '#mode-title' ).text(), 'album', G.list.name ]; - } - } - if ( !mpccmd ) mpccmd = []; - var sleep = G.mode.slice( -5 ) === 'radio' ? 1 : 0.2; - if ( G.status.state === 'play' && G.status.webradio ) sleep += 1; - var contextCommand = { - add : mpccmd - , playnext : mpccmd - , addplay : mpccmd.concat( [ 'addplay', sleep ] ) - , replace : mpccmd.concat( 'replace' ) - , replaceplay : mpccmd.concat( [ 'replaceplay', sleep ] ) - } - cmd = cmd.replace( /album|artist|composer|conductor|date|genre/g, '' ); - var command = contextCommand[ cmd ]; - if ( cmd === 'add' ) { - var title = 'Add to Playlist'; - } else if ( cmd === 'addplay' ) { - var title = 'Add to Playlist and play'; - } else if ( cmd === 'playnext' ) { - var title = 'Add to Playlist to play next'; - } else { - var title = 'Replace playlist'+ ( cmd === 'replace' ? '' : ' and play' ); - } - if ( G.list.li.hasClass( 'licover' ) ) { - var msg = G.list.li.find( '.lialbum' ).text() - +''+ G.list.li.find( '.liartist' ).text() +''; - } else if ( G.list.li.find( '.li1' ).length ) { - var msg = G.list.li.find( '.li1' )[ 0 ].outerHTML - + G.list.li.find( '.li2' )[ 0 ].outerHTML; - msg = msg.replace( '', '' ).replace( '', '' ); - } else { - var msg = G.list.li.find( '.lipath' ).text() || G.list.li.find( '.liname' ).text(); - } - if ( G.display.plclear && ( cmd === 'replace' || cmd === 'replaceplay' ) ) { - infoReplace( function() { - addReplace( cmd, command, title, msg ); - } ); - } else { - addReplace( cmd, command, title, msg ); - } -} ); From 14278081b2870d5b2e39b374fb748b80b29cc961 Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 18:37:31 +0700 Subject: [PATCH 21/36] u --- srv/http/assets/js/settings.js | 44 +++++++++++++++----------------- srv/http/bash/settings/system.sh | 1 + 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/srv/http/assets/js/settings.js b/srv/http/assets/js/settings.js index 63f7a82f0..791e0c0b8 100644 --- a/srv/http/assets/js/settings.js +++ b/srv/http/assets/js/settings.js @@ -6,7 +6,7 @@ function bash( command, callback, json ) { if ( cmd0 === 'cmd' ) { var filesh = 'cmd'; command.shift(); - } else if ( cmd0 === 'pkgstatus' ) { + } else if ( cmd0 === 'pkgstatus' || cmd0 === 'rebootlist' ) { var filesh = 'settings/system'; } else { var filesh = 'settings/'+ page; @@ -416,32 +416,28 @@ $( '.container' ).on( 'click', '.status', function( e ) { if ( !$this.hasClass( 'single' ) ) currentStatus( $this.data( 'status' ) ); } ); $( '.close' ).click( function() { - if ( page === 'networks' ) { - clearTimeout( G.timeoutScan ); - bash( 'killall networks-scan.sh &> /dev/null' ); - } else if ( page === 'system' ) { - bash( [ 'rebootlist' ], function( list ) { - if ( !list ) { + bash( [ 'rebootlist' ], function( list ) { + if ( !list ) { + location.href = '/'; + return + } + + info( { + icon : page + , title : 'System Setting' + , message : 'Reboot required for:

' + +'
'+ list +'
' + , cancel : function() { + bash( 'rm -f /srv/http/data/shm/reboot /srv/http/data/tmp/backup.*' ); location.href = '/'; - } else { - info( { - icon : page - , title : 'System Setting' - , message : 'Reboot required for:

' - +'
'+ list +'
' - , cancel : function() { - bash( 'rm -f /srv/http/data/shm/reboot /srv/http/data/tmp/backup.*' ); - location.href = '/'; - } - , okcolor : orange - , oklabel : 'Reboot' - , ok : function() { - bash( [ 'cmd', 'power', 'reboot' ] ); - } - } ); + } + , okcolor : orange + , oklabel : 'Reboot' + , ok : function() { + bash( [ 'cmd', 'power', 'reboot' ] ); } } ); - } + } ); } ); $( '#button-data' ).click( function() { if ( !G ) return diff --git a/srv/http/bash/settings/system.sh b/srv/http/bash/settings/system.sh index 320412141..9e83d5e82 100644 --- a/srv/http/bash/settings/system.sh +++ b/srv/http/bash/settings/system.sh @@ -730,6 +730,7 @@ reserved=$reserved" > $dirsystem/powerbutton.conf fi ;; rebootlist ) + killall networks-scan.sh &> /dev/null [[ -e $dirshm/reboot ]] && cat $dirshm/reboot | sort -u ;; relaysdisable ) From fc5d1c8e68bf8884ea9543d52d8bee0bd511c4dc Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 19:24:45 +0700 Subject: [PATCH 22/36] Update system.js --- srv/http/assets/js/system.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/srv/http/assets/js/system.js b/srv/http/assets/js/system.js index 3d7a2b934..ad6491b07 100644 --- a/srv/http/assets/js/system.js +++ b/srv/http/assets/js/system.js @@ -911,6 +911,7 @@ function infoMount( values ) { , title : shareddata ? 'Shared Data Server' : 'Add Network Storage' , content : htmlmount , values : values || [ 'cifs', '', ipsub, '', '', '', '', true ] + , checkblank : [ 0, 1, 2 ] , beforeshow : function() { $( '#infoContent td' ).eq( 0 ).css( 'width', 90 ); $( '#infoContent td' ).eq( 1 ).css( 'width', 230 ); @@ -952,7 +953,7 @@ function infoMount( values ) { } ); $share.on( 'keyup paste', function() { setTimeout( function() { - var slash = $( '#infoContent input[type=radio]:checked' ).val() === 'cifs' ? /[\/\\]/g : /\\//g; + var slash = $( '#infoContent input[type=radio]:checked' ).val() === 'cifs' ? /[\/\\]/g : /\\/g; $share.val( $share.val().replace( slash, '' ) ); }, 0 ); } ); From 851deb129b36fb08b12e40d11cac80efeab78e41 Mon Sep 17 00:00:00 2001 From: rern Date: Sun, 9 Oct 2022 21:45:04 +0700 Subject: [PATCH 23/36] u --- srv/http/assets/css/common.css | 2 +- srv/http/assets/fonts/rern.woff2 | Bin 16780 -> 16852 bytes srv/http/bash/status.sh | 9 +++++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/srv/http/assets/css/common.css b/srv/http/assets/css/common.css index 7ead0b4f4..883d05d8b 100644 --- a/srv/http/assets/css/common.css +++ b/srv/http/assets/css/common.css @@ -1,6 +1,6 @@ @font-face { font-family: rern; - src: url( '/assets/fonts/rern.2022101000.woff2' ) format( 'woff2' ); + src: url( '/assets/fonts/rern.2022101100.woff2' ) format( 'woff2' ); font-display: block; font-style: normal; font-weight: normal; diff --git a/srv/http/assets/fonts/rern.woff2 b/srv/http/assets/fonts/rern.woff2 index 114c0eccdfa9b294974b9b873c8c7bc9309601c8..8af781cb982242407d4253c37ff2d78ad21af9c0 100644 GIT binary patch literal 16852 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 T+~X7g)?ge!43ssH`nL)IZM)KG literal 16780 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 $dirshm/radio filenoext=/data/webradio/img/$urlname pathnoext=/srv/http$filenoext if [[ -e $pathnoext.jpg ]]; then - stationcover=$filenoext.$date.jpg + type=jpg elif [[ -e $pathnoext.gif ]]; then - stationcover=$filenoext.$date.gif + type=gif + fi + if [[ $urlname == *\?* ]]; then # cannot bust: url with ?param=123... + stationcover=${filenoext//\?/%3F}.$type?v=$date + else + stationcover=$filenoext.$date.$type fi fi status=$( grep -E -v '^, *"state"|^, *"webradio".*true|^, *"webradio".*false' <<< "$status" ) From 57e9f89439e0be91d2fc3db93af56dbcba8e8e0c Mon Sep 17 00:00:00 2001 From: rern Date: Mon, 10 Oct 2022 12:42:36 +0700 Subject: [PATCH 24/36] Update info.js --- srv/http/assets/js/info.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/srv/http/assets/js/info.js b/srv/http/assets/js/info.js index a48671729..ca89abb63 100644 --- a/srv/http/assets/js/info.js +++ b/srv/http/assets/js/info.js @@ -203,12 +203,11 @@ Note: } function infoReset( fn ) { if ( O.infoscroll ) $( 'html, body' ).scrollTop( O.infoscroll ); - if ( !O.buttonnoreset ) { - $( '#infoOverlay' ) - .addClass( 'hide' ) - .empty(); - } - if ( typeof fn === 'function' ) setTimeout( fn, 0 ); + if ( !O.buttonnoreset ) $( '#infoOverlay' ).addClass( 'hide' ); + setTimeout( function() { + if ( typeof fn === 'function' ) fn(); + $( '#infoOverlay' ).empty(); + }, 0 ); } O = {} From 47686d3f0bee32e949cdf77295a8139fb5c879ae Mon Sep 17 00:00:00 2001 From: rern Date: Mon, 10 Oct 2022 14:40:53 +0700 Subject: [PATCH 25/36] Update system.sh --- srv/http/bash/settings/system.sh | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/srv/http/bash/settings/system.sh b/srv/http/bash/settings/system.sh index 9e83d5e82..c1cd24423 100644 --- a/srv/http/bash/settings/system.sh +++ b/srv/http/bash/settings/system.sh @@ -91,7 +91,7 @@ sharedDataSet() { systemctl restart mpd sharedDataIPlist pushRefresh - pusrstream refresh '{"page":"features","shareddata":true}' + pushstream refresh '{"page":"features","shareddata":true}' } soundProfile() { if [[ $1 == reset ]]; then @@ -512,6 +512,7 @@ mirrorlist ) }' ;; mount ) + ['mount', 'cifs', 'data', '192.168.1.9', 'data', '', '', '', true] protocol=${args[1]} mountpoint="/mnt/MPD/NAS/${args[2]}" ip=${args[3]} @@ -519,17 +520,14 @@ mount ) user=${args[5]} password=${args[6]} extraoptions=${args[7]} - update=${args[8]} - shareddata=${args[9]} + shareddata=${args[8]} ! ping -c 1 -w 1 $ip &> /dev/null && echo "IP address not found: $ip" && exit - if [[ -e $mountpoint ]]; then - [[ $( ls "$mountpoint" ) ]] && echo "Mount name $mountpoint not empty." && exit - - else - mkdir "$mountpoint" - fi + [[ $( ls "$mountpoint" ) ]] && echo "Mount name $mountpoint not empty." && exit + + umount -ql "$mountpoint" + mkdir -p "$mountpoint" chown mpd:audio "$mountpoint" if [[ $protocol == cifs ]]; then source="//$ip/$directory" @@ -565,7 +563,7 @@ ${source// /\\040} ${mountpoint// /\\040} $protocol ${options// /\\040} 0 0 sleep 1 mount | grep -q "$mountpoint" && break done - [[ $shareddata ]] && sharedDataSet || pushRefresh + [[ $shareddata == true ]] && sharedDataSet || pushRefresh ;; mpdoleddisable ) rm $dirsystem/mpdoled @@ -817,6 +815,7 @@ shareddataconnect ) dir="/mnt/MPD/NAS/$( basename "$path" )" [[ $( ls "$dir" ) ]] && echo "Directory not empty: $dir" && exit + umount -ql "$dir" done options="nfs defaults,noauto,bg,soft,timeo=5 0 0" fstab=$( cat /etc/fstab ) @@ -855,13 +854,18 @@ shareddatadisconnect ) rm -f $dirshareddata /mnt/MPD/NAS/.mpdignore sed -i "/$( ipGet )/ d" $filesharedip mpc -q clear - ipserver=$( grep $dirshareddata /etc/fstab | cut -d: -f1 ) - readarray -t dirs <<< $( awk '/^'$ipserver'/ {print $2}' /etc/fstab | sed 's/\\040/ /g' ) + if [[ $( readlink $dirshareddata ) == $dirdata ]]; then + ipserver=$( grep $dirshareddata /etc/fstab | cut -d: -f1 ) + fstab=$( sed "/^$ipserver/ d" /etc/fstab ) + readarray -t dirs <<< $( awk '/^'$ipserver'/ {print $2}' /etc/fstab | sed 's/\\040/ /g' ) + else + fstab=$( sed "/^$dirshareddata/ d" /etc/fstab ) + dirs=( dirshareddata ) + fi for dir in "${dirs[@]}"; do umount -l "$dir" rmdir "$dir" &> /dev/null done - fstab=$( sed "/^$ipserver/ d" /etc/fstab ) echo "$fstab" | column -t > /etc/fstab systemctl daemon-reload systemctl restart mpd From d52e28d70dc8803df9e8bc67508d72a1c2c9da3b Mon Sep 17 00:00:00 2001 From: rern Date: Mon, 10 Oct 2022 14:49:36 +0700 Subject: [PATCH 26/36] Update main.js --- srv/http/assets/js/main.js | 1 - 1 file changed, 1 deletion(-) diff --git a/srv/http/assets/js/main.js b/srv/http/assets/js/main.js index 89cc042ba..78dd056b0 100644 --- a/srv/http/assets/js/main.js +++ b/srv/http/assets/js/main.js @@ -4,7 +4,6 @@ var G = { , apikeylastfm : 'd666cd06ec4fcf84c3b86279831a1c8e' , sharedsecret : '390372d3a1f60d4030e2a612260060e0' , bioartist : [] - , bookmarkedit : 0 , coverart : '/assets/img/coverart.svg' , coversave : 0 , covervu : '/assets/img/vu.svg' From b6903a35c9eb179e37406b49d4c84ddf126cc405 Mon Sep 17 00:00:00 2001 From: rern Date: Mon, 10 Oct 2022 15:32:53 +0700 Subject: [PATCH 27/36] u --- srv/http/assets/js/function.js | 4 +-- srv/http/assets/js/main.js | 33 ++++++++++++------------ srv/http/index-body.php | 47 +--------------------------------- srv/http/mpdlibrary.php | 41 +++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 64 deletions(-) diff --git a/srv/http/assets/js/function.js b/srv/http/assets/js/function.js index a2b5c016d..7a93d5d1f 100644 --- a/srv/http/assets/js/function.js +++ b/srv/http/assets/js/function.js @@ -573,8 +573,8 @@ function getPlaybackStatus( withdisplay ) { if ( G.playback ) { displayPlayback(); } else if ( G.library ) { - if ( !$( '#lib-search-close' ).text() && !G.librarylist ) { - $.post( 'index-body.php', { modelist: 1 }, function( html ) { + if ( !G.librarylist ) { + $.post( 'mpdlibrary.php', { query: 'home' }, function( html ) { $( '#lib-mode-list' ).html( html ); renderLibrary(); } ); diff --git a/srv/http/assets/js/main.js b/srv/http/assets/js/main.js index 78dd056b0..4b758f83a 100644 --- a/srv/http/assets/js/main.js +++ b/srv/http/assets/js/main.js @@ -437,15 +437,18 @@ $( '#addons' ).click( function () { loader(); } ); $( '#library, #button-library' ).click( function() { - $( '#lib-path span' ).removeClass( 'hide' ); - if ( !$( '#lib-search-input' ).val() ) $( '#lib-search-close' ).empty(); - if ( G.library ) { - if ( G.librarylist ) G.scrolltop[ $( '#lib-path .lipath' ).text() ] = $( window ).scrollTop(); - renderLibrary(); - } else { - switchPage( 'library' ); - if ( G.status.updating_db ) banner( 'Library Database', 'Update ...', 'refresh-library blink' ); - } + $.post( 'mpdlibrary.php', { query: 'home' }, function( html ) { + $( '#lib-mode-list' ).html( html ); + $( '#lib-path span' ).removeClass( 'hide' ); + if ( !$( '#lib-search-input' ).val() ) $( '#lib-search-close' ).empty(); + if ( G.library ) { + if ( G.librarylist ) G.scrolltop[ $( '#lib-path .lipath' ).text() ] = $( window ).scrollTop(); + renderLibrary(); + } else { + switchPage( 'library' ); + if ( G.status.updating_db ) banner( 'Library Database', 'Update ...', 'refresh-library blink' ); + } + } ); } ); $( '#playback' ).click( function() { if ( G.playback && ( G.wH - $( '#coverart' )[ 0 ].getBoundingClientRect().bottom ) < 30 ) { @@ -1313,7 +1316,9 @@ $( '#button-lib-back' ).click( function() { } } } ); -$( '.mode' ).click( function() { +$( '#lib-mode-list' ).click( function( e ) { + if ( !G.press && $( '.bkedit' ).length && !$( e.target ).hasClass( 'bkedit' ) ) setBookmarkEdit(); +} ).on( 'click', '.mode', function() { var $this = $( this ); G.mode = $this.data( 'mode' ); $( '#lib-search-close' ).click(); @@ -1385,11 +1390,7 @@ $( '.mode' ).click( function() { query.path = G.mode.slice( -5 ) === 'radio' ? '' : path; query.modetitle = path; if ( query.query !== 'ls' ) G.query.push( query ); -} ); -$( '#lib-mode-list' ).click( function( e ) { - if ( !G.press && $( '.bkedit' ).length && !$( e.target ).hasClass( 'bkedit' ) ) setBookmarkEdit(); -} ); -$( '#lib-mode-list' ).on( 'click', '.mode-bookmark', function( e ) { // delegate - id changed on renamed +} ).on( 'click', '.mode-bookmark', function( e ) { // delegate - id changed on renamed $( '#lib-search-close' ).click(); if ( G.press || $( '.bkedit' ).length ) return @@ -1502,7 +1503,7 @@ $( '#lib-mode-list' ).on( 'click', '.mode-bookmark', function( e ) { // delegate } } ); } ) -$( '.mode-bookmark' ).press( setBookmarkEdit ); +$( '#lib-mode-list' ).press( '.mode-bookmark', setBookmarkEdit ); new Sortable( document.getElementById( 'lib-mode-list' ), { // onChoose > onClone > onStart > onMove > onChange > onUnchoose > onUpdate > onSort > onEnd ghostClass : 'lib-sortable-ghost' diff --git a/srv/http/index-body.php b/srv/http/index-body.php index 448a25483..617e08582 100644 --- a/srv/http/index-body.php +++ b/srv/http/index-body.php @@ -47,51 +47,6 @@ exit; } - -// library home blocks -$modes = [ 'Album', 'Artist', 'Album Artist', 'Composer', 'Conductor', 'Date', 'Genre', 'Latest', 'NAS', 'SD', 'USB', 'Playlists', 'Web Radio', 'DAB Radio' ]; -$htmlmode = ''; -foreach( $modes as $mode ) { - $lipath = str_replace( ' ', '', $mode ); - $modeLC = strtolower( $lipath ); - $htmlmode.= - '
' - .'
' - .''.$modeLC.'' - .'' - .'' - .''.$mode.'' - .'
' - .'
'; -} -// bookmarks -$dir = '/srv/http/data/bookmarks'; -$files = array_slice( scandir( $dir ), 2 ); // remove ., .. -if ( count( $files ) ) { - foreach( $files as $name ) { - $data = file( $dir.'/'.str_replace( '|', '/', $name ), FILE_IGNORE_NEW_LINES ); - $bkpath = $data[ 0 ]; - $coverart = $data[ 1 ] ?? ''; - if ( $coverart ) { - $coverart = substr( $coverart, 0, -3 ).time().substr( $coverart, -4 ); - $icon = ''; - } else { - $icon = ''.$name.''; - } - $htmlmode.= - '
' - .'
' - .''.$bkpath.'' - .$icon - .'
' - .'
'; - } -} -if ( $_POST[ 'modelist' ] ) { // for refresh on page visible - echo $htmlmode; - exit; -} - // context menus function menucommon( $add, $replace ) { $htmlcommon = 'Add'; @@ -365,7 +320,7 @@ function htmlmenu( $menulist, $mode ) {
-
+
diff --git a/srv/http/mpdlibrary.php b/srv/http/mpdlibrary.php index f314587f4..eee477626 100644 --- a/srv/http/mpdlibrary.php +++ b/srv/http/mpdlibrary.php @@ -88,6 +88,47 @@ htmlFind( $lists, $f ); } break; +case 'home': + $modes = [ 'Album', 'Artist', 'Album Artist', 'Composer', 'Conductor', 'Date', 'Genre', 'Latest', 'NAS', 'SD', 'USB', 'Playlists', 'Web Radio', 'DAB Radio' ]; + $htmlmode = ''; + foreach( $modes as $mode ) { + $lipath = str_replace( ' ', '', $mode ); + $modeLC = strtolower( $lipath ); + $htmlmode.= + '
' + .'
' + .''.$modeLC.'' + .'' + .'' + .''.$mode.'' + .'
' + .'
'; + } + // bookmarks + $dir = '/srv/http/data/bookmarks'; + $files = array_slice( scandir( $dir ), 2 ); // remove ., .. + if ( count( $files ) ) { + foreach( $files as $name ) { + $data = file( $dir.'/'.str_replace( '|', '/', $name ), FILE_IGNORE_NEW_LINES ); + $bkpath = $data[ 0 ]; + $coverart = $data[ 1 ] ?? ''; + if ( $coverart ) { + $coverart = substr( $coverart, 0, -3 ).time().substr( $coverart, -4 ); + $icon = ''; + } else { + $icon = ''.$name.''; + } + $htmlmode.= + '
' + .'
' + .''.$bkpath.'' + .$icon + .'
' + .'
'; + } + } + echo $htmlmode; + break; case 'list': $filemode = '/srv/http/data/mpd/'.$mode; if ( $mode === 'album' && exec( 'grep "albumbyartist.*true" /srv/http/data/system/display' ) ) $filemode.= 'byartist'; From 574ddd90fc9a698f4fc93e98331f21dc2efcdc87 Mon Sep 17 00:00:00 2001 From: rern Date: Mon, 10 Oct 2022 15:40:13 +0700 Subject: [PATCH 28/36] Update system.sh --- srv/http/bash/settings/system.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srv/http/bash/settings/system.sh b/srv/http/bash/settings/system.sh index c1cd24423..55d0e0834 100644 --- a/srv/http/bash/settings/system.sh +++ b/srv/http/bash/settings/system.sh @@ -870,7 +870,7 @@ shareddatadisconnect ) systemctl daemon-reload systemctl restart mpd pushRefresh - pusrstream refresh '{"page":"features","shareddata":false}' + pushstream refresh '{"page":"features","shareddata":false}' if [[ ! $disable ]]; then echo $ipserver > $dirsystem/sharedipserver # for sshpass reconnect pushstreamNotify 'Server rAudio' 'Offline ...' rserver From 004ea4d2545ce0ebb5e461b02ab51d5ac4ed559e Mon Sep 17 00:00:00 2001 From: rern Date: Mon, 10 Oct 2022 16:51:14 +0700 Subject: [PATCH 29/36] u --- srv/http/settings/features.php | 2 ++ srv/http/settings/system.php | 13 +++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/srv/http/settings/features.php b/srv/http/settings/features.php index 3a8bb661d..eb56eaca6 100644 --- a/srv/http/settings/features.php +++ b/srv/http/settings/features.php @@ -288,9 +288,11 @@ - Must be set to static IP address which should be set on router. - Existing list in | I^library^I | I^usb^I USB | will display in | I^networks^I NAS | once update finished. - On reboot / power off, Shared Data on clients will be temporarily disabled > re-enabled once the server is back online. + • rAudio Shared Data clients: - | I^system^I System | Settings and Data | Shared Data I^networks^I | • rAudio - Automatically setup: discover, connect shared files and data + • Windows NFS clients: - Windows Features > Services for NFS > Client for NFS - Enable - $fileexplorer diff --git a/srv/http/settings/system.php b/srv/http/settings/system.php index 5a562c9f8..cdf27146a 100644 --- a/srv/http/settings/system.php +++ b/srv/http/settings/system.php @@ -92,15 +92,14 @@ ] ); ?>
    -
    Icon context menu: Unmount / Re-mount / Forget / Info / Share +
    | | | Context menu available USB drives: • Will be found and mounted automatically. Network shares: • Must be manually configured. - • If mount failed, try in SSH terminal: - (replace YELLOW with actual values) + • If mount failed, try in SSH terminal: (replace YELLOW with actual values)
     mkdir -p "/mnt/MPD/NAS/NAME"
     # CIFS: (no user - username=guest, no password - password="")
    @@ -295,7 +294,7 @@
     		, 'help'     => <<< HTML
     I^gear^I
      • NTP server: For time sync
    - • Package mirror server: For system upgrade pacman -Syu
    + • Package mirror server
     HTML
     	]
     	, [
    @@ -349,10 +348,12 @@
     	- Data - Audio CD, bookmarks, lyrics, saved playlists and Web Radio
     	- Show / hide items
     	- Display order of Library home
    - • rAudio as server:
    +	
    + • rAudio as server: (Alternative 1)
     	Server: | I^features^I Features | Server rAudio I^rserver^I |
     	Clients: | Shared Data I^networks^I | • rAudio |
    - • Other servers: 
    +	
    + • Other servers: (Alternative 2)
     	Server: Create a share for data with full permissions
     		- Linux: NFS 777, CIFS/SMB read only = no
     		- Windows: Everyone - Full Control (Sharing + Security)
    
    From 44e787f54d5eea92a8bc93190c744db90d1d2b50 Mon Sep 17 00:00:00 2001
    From: rern 
    Date: Mon, 10 Oct 2022 18:15:57 +0700
    Subject: [PATCH 30/36] u
    
    ---
     srv/http/assets/js/system.js     | 9 +++++++++
     srv/http/bash/settings/system.sh | 2 +-
     srv/http/settings/system.php     | 4 +++-
     3 files changed, 13 insertions(+), 2 deletions(-)
    
    diff --git a/srv/http/assets/js/system.js b/srv/http/assets/js/system.js
    index ad6491b07..a6f7a3a7d 100644
    --- a/srv/http/assets/js/system.js
    +++ b/srv/http/assets/js/system.js
    @@ -131,6 +131,15 @@ $( '#menu a' ).click( function() {
     		var icon = 'usbdrive';
     		var title = 'Local Mount';
     	}
    +	if ( G.shareddata && mountpoint === '/mnt/MPD/NAS/data' ) {
    +		info( {
    +			  icon    : 'networks'
    +			, title   : 'Network Storage'
    +			, message : 'Shared Data  is currently enabled.'
    +		} );
    +		return
    +	}
    +	
     	switch ( cmd ) {
     		case 'forget':
     			notify( title, 'Forget ...', icon );
    diff --git a/srv/http/bash/settings/system.sh b/srv/http/bash/settings/system.sh
    index 55d0e0834..99c6b44fd 100644
    --- a/srv/http/bash/settings/system.sh
    +++ b/srv/http/bash/settings/system.sh
    @@ -860,7 +860,7 @@ shareddatadisconnect )
     		readarray -t dirs <<< $( awk '/^'$ipserver'/ {print $2}' /etc/fstab | sed 's/\\040/ /g' )
     	else
     		fstab=$( sed "/^$dirshareddata/ d" /etc/fstab )
    -		dirs=( dirshareddata )
    +		dirs=( $dirshareddata )
     	fi
     	for dir in "${dirs[@]}"; do
     		umount -l "$dir"
    diff --git a/srv/http/settings/system.php b/srv/http/settings/system.php
    index cdf27146a..d8cb17d54 100644
    --- a/srv/http/settings/system.php
    +++ b/srv/http/settings/system.php
    @@ -92,7 +92,9 @@
     ] );
     ?>
     	
      -
      | | | Context menu available +
      Context menu: +| | Info +| | Unmount, Re-mount, Forget (Hidden if Shared Data is enabled) USB drives: • Will be found and mounted automatically. From fcd3e3149f39dadaa1a06dbf998d6edf55b1c80d Mon Sep 17 00:00:00 2001 From: rern Date: Mon, 10 Oct 2022 18:25:09 +0700 Subject: [PATCH 31/36] Update system.sh --- srv/http/bash/settings/system.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srv/http/bash/settings/system.sh b/srv/http/bash/settings/system.sh index 99c6b44fd..c8bf2f327 100644 --- a/srv/http/bash/settings/system.sh +++ b/srv/http/bash/settings/system.sh @@ -856,10 +856,10 @@ shareddatadisconnect ) mpc -q clear if [[ $( readlink $dirshareddata ) == $dirdata ]]; then ipserver=$( grep $dirshareddata /etc/fstab | cut -d: -f1 ) - fstab=$( sed "/^$ipserver/ d" /etc/fstab ) + fstab=$( grep -v ^$ipserver /etc/fstab ) readarray -t dirs <<< $( awk '/^'$ipserver'/ {print $2}' /etc/fstab | sed 's/\\040/ /g' ) else - fstab=$( sed "/^$dirshareddata/ d" /etc/fstab ) + fstab=$( grep -v $dirshareddata /etc/fstab ) dirs=( $dirshareddata ) fi for dir in "${dirs[@]}"; do From 3c2f60616c9e813acc40fa22541835089a05f58d Mon Sep 17 00:00:00 2001 From: rern Date: Mon, 10 Oct 2022 18:37:09 +0700 Subject: [PATCH 32/36] Update system.php --- srv/http/settings/system.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srv/http/settings/system.php b/srv/http/settings/system.php index d8cb17d54..7cc91269e 100644 --- a/srv/http/settings/system.php +++ b/srv/http/settings/system.php @@ -204,7 +204,7 @@ , 'help' => <<< HTML LCD module - display playback data • Support 16x2 and 20x4 LCD modules. -Iarning yl^I LCD with I²C backpack must be modified: 5V to 3.3V I²C and 5V LCD +I^warning yl^I LCD with I²C backpack must be modified: 5V to 3.3V I²C and 5V LCD HTML ] , [ From 0a992aabdbf5ebf2041034d98585e8284d6247dc Mon Sep 17 00:00:00 2001 From: rern Date: Mon, 10 Oct 2022 18:46:09 +0700 Subject: [PATCH 33/36] Update install.sh --- install.sh | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/install.sh b/install.sh index acc7f15cc..d173dae5f 100644 --- a/install.sh +++ b/install.sh @@ -7,19 +7,6 @@ alias=r1 # 20221007 grep -q hard,intr /etc/fstab && sed -i '/hard,intr/soft/' /etc/fstab -dir=/srv/http/shareddata -if [[ -e $dir ]]; then - echo data > /mnt/MPD/NAS/.mpdignore - mkdir -p $dirshareddata - mv $dir/iplist > $filesharedip - chmod 777 $filesharedip - umount -l $dir - sed -i "s|$dir|$dirshareddata|" /etc/fstab - systemctl daemon-reload - mount $dirshareddata - rmdir $dir -fi - [[ -e $dirsystem/hddspindown ]] && mv $dirsystem/{hddspindown,apm} if [[ ! -e /boot/kernel.img ]]; then @@ -84,3 +71,6 @@ $dirbash/settings/system.sh dirpermissions installfinish #------------------------------------------------------------------------------- + +# 20221010 +[[ -e /srv/http/shareddata ]] && echo -e "$info Shared Data must be disabled and setup again." From c79cc66e2a1aade1438ac6ea59b48c25c45b457f Mon Sep 17 00:00:00 2001 From: rern Date: Tue, 11 Oct 2022 10:45:55 +0700 Subject: [PATCH 34/36] u --- srv/http/assets/js/function.js | 4 +--- srv/http/bash/status.sh | 10 ++++++---- srv/http/mpdlibrary.php | 4 +++- srv/http/mpdplaylist.php | 6 ++++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/srv/http/assets/js/function.js b/srv/http/assets/js/function.js index 7a93d5d1f..7432af567 100644 --- a/srv/http/assets/js/function.js +++ b/srv/http/assets/js/function.js @@ -1248,9 +1248,7 @@ function renderPlaylist( data ) { setPlaylistScroll(); } else { G.htmlplaylist = data.html; - var timestamp = Math.floor( Date.now() / 1000 ); - var html = data.html.replaceAll( 'thumb.jpg', 'thumb.'+ timestamp +'.jpg' ); - $( '#pl-list' ).html( html +'

      ' ).promise().done( function() { + $( '#pl-list' ).html( data.html +'

      ' ).promise().done( function() { G.status.pllength = $( '#pl-list li' ).length; setPlaylistScroll(); imageLoad( 'pl-list' ); diff --git a/srv/http/bash/status.sh b/srv/http/bash/status.sh index 9d23ed888..0a835ac79 100644 --- a/srv/http/bash/status.sh +++ b/srv/http/bash/status.sh @@ -336,10 +336,12 @@ $radiosampling" > $dirshm/radio elif [[ -e $pathnoext.gif ]]; then type=gif fi - if [[ $urlname == *\?* ]]; then # cannot bust: url with ?param=123... - stationcover=${filenoext//\?/%3F}.$type?v=$date - else - stationcover=$filenoext.$date.$type + if [[ $type ]]; then + if [[ $urlname == *\?* ]]; then # cannot bust: url with ?param=... + stationcover=${filenoext//\?/%3F}.$type?v=$date + else + stationcover=$filenoext.$date.$type + fi fi fi status=$( grep -E -v '^, *"state"|^, *"webradio".*true|^, *"webradio".*false' <<< "$status" ) diff --git a/srv/http/mpdlibrary.php b/srv/http/mpdlibrary.php index eee477626..e6b9a8a49 100644 --- a/srv/http/mpdlibrary.php +++ b/srv/http/mpdlibrary.php @@ -465,13 +465,15 @@ function htmlRadio( $subdirs, $files, $dir ) { usort( $array, function( $a, $b ) { return strnatcasecmp( $a->sort, $b->sort ); } ); + $time = time(); foreach( $array as $each ) { $index = strtoupper( mb_substr( $each->sort, 0, 1, 'UTF-8' ) ); $indexes[] = $index; $datacharset = $each->charset ? ' data-charset="'.$each->charset.'"' : ''; $url = $each->url; $urlname = str_replace( '/', '|', $url ); - $thumbsrc = rawurlencode( '/data/'.$gmode.'/img/'.$urlname.'-thumb.'.time().'.jpg' ); + $thumbsrc = '/data/'.$gmode.'/img/'.rawurlencode( $urlname ).'-thumb.'; + $thumbsrc.= strpos( $urlname, '?' ) ? 'jpg?v='.$time : $time.'.jpg'; $liname = $each->name; $name = $searchmode ? preg_replace( "/($string)/i", '$1', $liname ) : $liname; $html.= '
    • diff --git a/srv/http/mpdplaylist.php b/srv/http/mpdplaylist.php index 111989fa0..ecaa73a90 100644 --- a/srv/http/mpdplaylist.php +++ b/srv/http/mpdplaylist.php @@ -181,7 +181,7 @@ function htmlTrack( $lists, $plname = '' ) { $class = 'file'; $discid = ''; $path = pathinfo( $file, PATHINFO_DIRNAME ); - $thumbsrc = '/mnt/MPD/'.rawurlencode( $path ).'/thumb.jpg' ; // replaced with icon on load error(faster than existing check) + $thumbsrc = '/mnt/MPD/'.rawurlencode( $path ).'/thumb.'.$time.'.jpg' ; // replaced with icon on load error(faster than existing check) $icon = 'music'; $htmlicon = ''; } else { @@ -228,7 +228,9 @@ function htmlTrack( $lists, $plname = '' ) { } if ( $stationname !== '' ) { $notsaved = 0; - $icon = ''; + $thumbsrc = '/data/'.$type.'/img/'.rawurlencode( $urlname ).'-thumb.'; + $thumbsrc.= strpos( $urlname, '?' ) ? 'jpg?v='.$time : $time.'.jpg'; + $icon = ''; } else { $notsaved = 1; $icon = ''; From ce2175f8cf3649dbd820083db9d7535ade21a5b1 Mon Sep 17 00:00:00 2001 From: rern Date: Tue, 11 Oct 2022 10:59:38 +0700 Subject: [PATCH 35/36] Add files via upload --- srv/http/bash/status.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/srv/http/bash/status.sh b/srv/http/bash/status.sh index 0a835ac79..168d05cd7 100644 --- a/srv/http/bash/status.sh +++ b/srv/http/bash/status.sh @@ -315,10 +315,13 @@ $radiosampling" > $dirshm/radio [[ ! $displaycover ]] && coverart= fi elif [[ $Title && $displaycover ]]; then - # split Artist - Title: Artist - Title (extra tag) or Artist: Title (extra tag) - readarray -t radioname <<< $( echo $Title | sed -E 's/ - |: /\n/' ) - Artist=${radioname[0]} - Title=${radioname[1]} + if [[ $Title == *" - "* ]]; then # split Artist - Title: Artist - Title (extra tag) or Artist: Title (extra tag) + readarray -t radioname <<< $( echo $Title | sed -E 's/ - |: /\n/' ) + Artist=${radioname[0]} + Title=${radioname[1]} + else + Artist=$station + fi # fetched coverart covername=$( echo "$Artist${Title/ (*}" | tr -d ' "`?/#&'"'" ) # remove ' (extra tag)' coverfile=$( ls $dirshm/webradio/$covername.* 2> /dev/null | head -1 ) From 340d7341ad5cb6d5f5bb57c3d505e199452b04ce Mon Sep 17 00:00:00 2001 From: rern Date: Tue, 11 Oct 2022 11:02:02 +0700 Subject: [PATCH 36/36] Add files via upload --- srv/http/bash/status.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srv/http/bash/status.sh b/srv/http/bash/status.sh index 168d05cd7..4de204ace 100644 --- a/srv/http/bash/status.sh +++ b/srv/http/bash/status.sh @@ -315,7 +315,7 @@ $radiosampling" > $dirshm/radio [[ ! $displaycover ]] && coverart= fi elif [[ $Title && $displaycover ]]; then - if [[ $Title == *" - "* ]]; then # split Artist - Title: Artist - Title (extra tag) or Artist: Title (extra tag) + if [[ $Title == *" - "* ]]; then # split 'Artist - Title' or 'Artist: Title' (extra tag) readarray -t radioname <<< $( echo $Title | sed -E 's/ - |: /\n/' ) Artist=${radioname[0]} Title=${radioname[1]} @@ -323,7 +323,7 @@ $radiosampling" > $dirshm/radio Artist=$station fi # fetched coverart - covername=$( echo "$Artist${Title/ (*}" | tr -d ' "`?/#&'"'" ) # remove ' (extra tag)' + covername=$( echo "$Artist${Title/ (*}" | tr -d ' "`?/#&'"'" ) # remove '... (extra tag)' coverfile=$( ls $dirshm/webradio/$covername.* 2> /dev/null | head -1 ) if [[ $coverfile ]]; then coverart=${coverfile:9}