Support playlist management
authorJoey Schulze <joey@infodrom.org>
Tue, 25 Dec 2018 00:29:03 +0000 (01:29 +0100)
committerJoey Schulze <joey@infodrom.org>
Wed, 26 Feb 2020 15:20:27 +0000 (16:20 +0100)
12 files changed:
class/ajax.class.php
class/cache.class.php
class/playlists.class.php [new file with mode: 0644]
html/index.php
html/main.js
html/musiikki.css
install-system
templates/main.phtml
templates/playlist.phtml [new file with mode: 0644]
templates/playlists.phtml [new file with mode: 0644]
templates/results.phtml
templates/search.phtml

index 8d11ee5..ec34f5e 100644 (file)
@@ -148,7 +148,8 @@ class AJAX {
 
        $list = $cache->findTitles($_POST['keyword']);
 
-       return $list;
+       return ['results' => $list,
+               'playlist' => $_SESSION['playlist']];
     }
 
     public static function getresultsAction()
@@ -156,4 +157,44 @@ class AJAX {
        return array();
     }
 
+    public static function playlistSelectAction()
+    {
+       $_SESSION['playlist'] = $_POST['name'];
+    }
+
+    public static function getplaylistsAction()
+    {
+       $playlists = new Playlists();
+       $selected = null;
+       if (array_key_exists('playlist', $_SESSION))
+           $selected = $_SESSION['playlist'];
+
+       return ['list' => $playlists->getList(),
+               'selected' => $selected];
+    }
+
+    public static function playlistAction()
+    {
+       $playlists = new Playlists();
+
+       return ['title' => $_POST['name'],
+               'list' => $playlists->getContents($_POST['name'])];
+    }
+
+    public function playlistNewAction()
+    {
+       $playlists = new Playlists();
+       $playlists->create($_POST['name']);
+    }
+
+    public function playlistAddAction()
+    {
+       $cache = new Cache();
+       $playlists = new Playlists();
+
+       if (!array_key_exists('playlist', $_SESSION))
+           return;
+
+       $playlists->add($_SESSION['playlist'], $cache->getRelativePath($_POST['id']));
+    }
 }
index 9cd885a..89b3ca8 100644 (file)
@@ -11,11 +11,24 @@ class Cache {
        $this->db = new PDO('sqlite:/'.$cache);
     }
 
+    public function getRelativePath($id)
+    {
+       $media_dir = Config::main()->get('media_dir_A');
+
+       $sql = sprintf("SELECT path FROM details WHERE id = %d", $id);
+       $sth = $this->db->query($sql);
+       if ($sth === false) return null;
+
+       $row = $sth->fetchObject();
+       $path = substr($row->PATH, strlen($media_dir)+1);
+       return $path;
+    }
+
     public function findTitles($keyword)
     {
        $media_dir = Config::main()->get('media_dir_A');
        $results = array('count' => 0, 'list' => []);
-       $sql = sprintf("SELECT path, title, timestamp, size FROM details WHERE path LIKE %s AND mime IS NOT NULL",
+       $sql = sprintf("SELECT id, path, title, timestamp, size FROM details WHERE path LIKE %s AND mime IS NOT NULL",
                       $this->db->quote('%'.$keyword.'%'));
 
        $sth = $this->db->query($sql);
@@ -41,7 +54,14 @@ class Cache {
 
            if (!array_key_exists($section, $results['list']))
                $results['list'][$section] = [];
-           $results['list'][$section][] = ['path' => dirname($path), 'name' => basename($path), 'title' => $row->TITLE];
+
+           $item = ['path' => dirname($path), 'name' => basename($path), 'title' => $row->TITLE];
+           if (array_key_exists('playlist', $_SESSION))
+               $item['id'] = $row->ID;
+           else
+               $item['id'] = '';
+
+           $results['list'][$section][] = $item;
        }
 
        return $results;
diff --git a/class/playlists.class.php b/class/playlists.class.php
new file mode 100644 (file)
index 0000000..2c0d329
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+
+class Playlists {
+    const EMPTYSTRING = 'not empty';
+
+    public function getList()
+    {
+       $dir = Config::main()->get('media_dir_A') . '/playlists';
+       $list = [];
+
+       foreach (new DirectoryIterator($dir) as $fileInfo) {
+           if ($fileInfo->isDot()) continue;
+           if ($fileInfo->isDir()) continue;
+           if ($fileInfo->getExtension() != 'm3u') continue;
+
+           $lines = preg_split('/\r?\n/', file_get_contents($fileInfo->getPathname()));
+           if ($lines[0] == static::EMPTYSTRING) {
+               $count = 0;
+           } else {
+               $count = count($lines) - 1;
+           }
+
+           $list[] = ['name' => $fileInfo->getBasename('.m3u'), 'count' => $count];
+       }
+
+       usort($list, function($a, $b){return strcmp($a['name'], $b['name']);});
+       return $list;
+    }
+
+    public function getContents($name)
+    {
+       $dir = Config::main()->get('media_dir_A') . '/playlists';
+       $list = [];
+
+       $path = $dir . '/' . $name . '.m3u';
+       if (file_exists($path)) {
+           $content = explode("\n", file_get_contents($path));
+           foreach ($content as $path) {
+               if (empty(trim($path))) continue;
+               if ($path == static::EMPTYSTRING) continue;
+
+               $fname = basename($path);
+
+               if (($pos = strrpos($fname, '.')) > strlen($fname)-10)
+                   $fname = substr($fname, 0, $pos);
+               $list[] = $fname;
+           }       
+       }
+
+       return $list;
+    }
+
+    public function add($name, $path)
+    {
+       $playlist = Config::main()->get('media_dir_A') . '/playlists/' . $name . '.m3u';
+       if (!file_exists($playlist)) return;
+
+       $content = explode("\n", file_get_contents($playlist));
+       $found = false;
+       foreach ($content as $line)
+           if ($line == $path)
+               $found = true;
+
+       if (!$found)
+           $content[] = $path;
+
+       if (($f = fopen($playlist.'.new', 'w')) !== false) {
+           foreach ($content as $line) {
+               if (empty(trim($line))) continue;
+               if ($line == static::EMPTYSTRING) continue;
+               fwrite($f, $line . "\n");
+           }
+           fclose($f);
+           rename($playlist.'.new', $playlist);
+       }
+       
+    }
+
+    public function create($name)
+    {
+       $playlist = Config::main()->get('media_dir_A') . '/playlists/' . $name . '.m3u';
+       if (file_exists($playlist)) return;
+
+       if (($f = fopen($playlist, 'w')) !== false) {
+           fwrite($f, static::EMPTYSTRING . "\n");
+           
+           fclose($f);
+       }
+    }
+}
index 694096a..4fb9fbb 100644 (file)
@@ -1,6 +1,9 @@
 <?php
 require_once(__DIR__.'/../class/autoloader.class.php');
 
+session_name('MUSIIKKI');
+session_start();
+
 if ($_SERVER['REQUEST_METHOD'] == 'GET') {
     $template = new Template('main');
     echo $template->render([]);
index fc57f28..25d2cb5 100644 (file)
@@ -42,9 +42,23 @@ function results_waiting()
 }
 setTimeout(results_waiting_stop,20000);
 
-function submit_search()
+function search_addto_playlist(event)
 {
-    if (!$('input#search_keyword').val().length)
+    var btn = $(this);
+    console.log($(this).attr('data-id'));
+
+    $.post('index.php',
+          {action: 'playlistAdd',
+           id: $(this).attr('data-id')},
+          function(data){
+              btn.hide();
+          });
+}
+
+function submit_search(e)
+{
+    var input = $(e).parent().find('#search_keyword');
+    if (!input.val().length)
        return false;
 
     $('div#results h1').text('Results.');
@@ -53,25 +67,39 @@ function submit_search()
     results_waiting();
 
     $.post('index.php',
-          'action=search&' + $('div.w3-container#search form').serialize(),
+          'action=search&keyword=' + encodeURI(input.val()),
           function(data){
               if (data) {
-                  $('div#results h1').text(data.count+' matches.');
+                  input.val('');
+                  if (data.playlist != null && data.playlist.length) {
+                      $('#results #playlist').find('b').text(data.playlist);
+                      $('#results #playlist').show();
+                  } else {
+                      $('#results #playlist').hide();
+                  }
+                  $('div#results h1').text(data.results.count+' matches.');
                   var result = $('<ul class="section">');
 
-                  for (section in data.list) {
+                  for (section in data.results.list) {
                       var dom_li = $('<li class="section">');
                       var dom_div = $('<div class="section">');
                       dom_div.append($('<strong class="w3-round">').text(section).click(toggle_section));
 
                       var dom_ul = $('<ul class="files">').hide();
-                      for (var i=0; i < data.list[section].length; i++) {
+                      for (var i=0; i < data.results.list[section].length; i++) {
                           var item = $('<li>');
                           if (i % 2 == 1)
                               item.addClass('bg');
-                          item.append($('<div class="it">').html(data.list[section][i].title));
-                          item.append($('<div class="ip">').html(data.list[section][i].path));
-                          item.append($('<div class="in">').html(data.list[section][i].name));
+                          item.append($('<div class="it">').html(data.results.list[section][i].title));
+                          item.append($('<div class="ip">').html(data.results.list[section][i].path));
+                          item.append($('<div class="in">').html(data.results.list[section][i].name));
+
+                          if (data.results.list[section][i].id.length) {
+                              var plus = $('<div class="plus w3-round">').text('+');
+                              plus.click(search_addto_playlist);
+                              plus.attr('data-id', data.results.list[section][i].id);
+                              item.append(plus);
+                          }
 
                           dom_ul.append(item);
                       }
@@ -91,3 +119,68 @@ function submit_search()
     return false;
 }
 
+function playlists_select(event)
+{
+    var name = $(this).attr('data-name');
+    $.post('index.php',
+          {action: 'playlistSelect',
+           name: name},
+          function(data){
+              $.post('index.php',
+                     {action: 'playlist',
+                      name: name},
+                     playlist_process);
+          });
+}
+
+function playlists_process(data)
+{
+    var ul = $('<div class="playlists">');
+    for (var i=0; i < data.list.length; i++) {
+       var li = $('<div class="w3-round item">');
+       li.attr('data-name', data.list[i].name);
+       li.click(playlists_select);
+       li.append($('<span style="float:left;">'+data.list[i].name+'</span>'));
+       li.append($('<span style="float:right;">'+data.list[i].count+'</span>'));
+       li.append($('<div style="clear:both;"></div>'));
+       if (data.selected && data.selected == name)
+           li.addClass('selected');
+       ul.append(li);
+    }
+    $('div#playlists div.w3-section').empty().append(ul);
+}
+
+function playlist_process(data)
+{
+    $('div#playlist #title').text(data.title+'.');
+    if (data.list.length == 0) {
+       var ol = $('<p>');
+       ol.text('Playlist ist noch leer.');
+    } else {
+       var ol = $('<ol class="playlist">');
+       for (var i=0; i < data.list.length; i++) {
+           var name = data.list[i];
+           var li = $('<li>');
+           li.text(name);
+           ol.append(li);
+       }
+    }
+    $('div#playlist div.w3-section').empty().append(ol);
+    open_page('playlist');
+}
+
+function submit_playlist()
+{
+    $.post('index.php',
+          {action: 'playlistNew',
+           name: $('div#playlists input#name').val()},
+          function(data){
+              $('div#playlists input#name').val('');
+              open_page('playlists');
+          });
+}
+
+
+$(function(){
+    register_callback('playlists', playlists_process);
+});
index 055a39b..3a0f9a7 100644 (file)
@@ -7,10 +7,26 @@ div#pageheader {
     padding-bottom: 5px;
 }
 
+/* Search */
 ul.section, ul.files {list-style-type: none; margin: 0; padding: 0;}
-ul.section li {margin-bottom: 0.2rem;}
+ul.section li {margin-bottom: 0.2rem; position: relative;}
 div.section strong {width: 100%; display: block; background: #87CEFF; padding: 0.1rem; cursor:pointer;}
 ul.files li {margin-bottom: 0.3rem; line-height: 1.15rem;}
 ul.files li div.it {font-size: 14px;}
 ul.files li div.ip, ul.files li div.in {font-size: 12px;}
 li.bg {background: #f0f0f0;}
+ul.files li div.plus {font-weight: bold; border:1px solid #CCC; position:absolute;top: 1rem; right: 1rem;
+                     padding-top:0.4rem; padding-bottom:0.4rem; padding-left:0.5rem; padding-right:0.5rem;}
+ul.files li div.plus:hover {background: #87CEFF;}
+#results #playlist {float:right; margin-top:0px;}
+
+/* Playlists */
+div.playlists {list-style-type: none; margin: 0; padding: 0;}
+div.playlists div.w3-round {padding: 0.4rem;border:1px solid #CCC; margin-bottom: 0.2rem; cursor:pointer;}
+div.playlists div.w3-round:hover {background: #87CEFF;}
+div.playlists div.w3-round.selected {background: #87CEFF;}
+div.playlists div.w3-round.selected:hover {background: white;}
+div.playlists div.item {display:block;}
+
+ol.playlist {padding: 0.8rem;}
+ol.playlist li {font-size: 12px;}
index 448a3b8..710319e 100755 (executable)
@@ -425,6 +425,9 @@ assert_minidlna()
 
     test -d /media/music/cache || install -d -m 755 /media/music/cache
     chown -R minidlna:minidlna /media/music/cache
+
+    test -d /media/music/music/playlists || install -d -m 755 /media/music/music/playlists
+    chown -R www-data:www-data /media/music/music/playlists
 }
 
 assert_lighttpd()
index cfec200..0036fc6 100644 (file)
@@ -24,6 +24,7 @@
   <div class="w3-bar-block" style="padding-top:5px;">
     <a href="/" onclick="open_page('home')" class="w3-bar-item w3-button w3-hover-white">Home</a>
     <a href="#" onclick="open_page('search')" class="w3-bar-item w3-button w3-hover-white">Search</a> 
+    <a href="#" onclick="open_page('playlists')" class="w3-bar-item w3-button w3-hover-white">Playlists</a>
   </div>
 </nav>
 
@@ -42,6 +43,8 @@
 <?php echo Template::html('main_home'); ?>
 <?php echo Template::html('search'); ?>
 <?php echo Template::html('results'); ?>
+<?php echo Template::html('playlists'); ?>
+<?php echo Template::html('playlist'); ?>
 
 <!-- End page content -->
 </div>
diff --git a/templates/playlist.phtml b/templates/playlist.phtml
new file mode 100644 (file)
index 0000000..8d7a521
--- /dev/null
@@ -0,0 +1,7 @@
+<!-- Playlist -->
+<div class="w3-container" id="playlist" style="margin-top:75px" style="display:none;">
+  <h1 class="w3-xlarge w3-text-blue"><b id="title">.</b></h1>
+  <hr style="width:50px;border:5px solid blue" class="w3-round">
+  <div class="w3-section">
+  </div>
+</div>
diff --git a/templates/playlists.phtml b/templates/playlists.phtml
new file mode 100644 (file)
index 0000000..01ac992
--- /dev/null
@@ -0,0 +1,10 @@
+<!-- Playlists -->
+<div class="w3-container" id="playlists" style="margin-top:75px" data-fetch="true" style="display:none;">
+  <h1 class="w3-xxxlarge w3-text-blue"><b>Playlists.</b></h1>
+  <hr style="width:50px;border:5px solid blue" class="w3-round">
+  <div class="w3-section">
+  </div>
+  <label>Neue Playlist (ab morgen verf├╝gbar)</label>
+  <input class="w3-input w3-border" id="name" requiblue="" type="text">
+  <button type="submit" class="w3-button w3-block w3-padding-large w3-blue w3-margin-bottom" onclick="return submit_playlist()">Anlegen</button>
+</div>
index 7dd7204..470d0b0 100644 (file)
@@ -1,7 +1,15 @@
-<!-- Search -->
+<!-- Results -->
 <div class="w3-container" id="results" style="margin-top:75px" style="display:none;">
-  <h1 class="w3-xxxlarge w3-text-blue"><b>Search.</b></h1>
+  <h6 class="w3-medium w3-text-blue" id="playlist" style="display:none;"><b></b></h6>
+  <h1 class="w3-xlarge w3-text-blue"><b>Search.</b></h1>
   <hr style="width:50px;border:5px solid blue" class="w3-round">
-  <div class="w3-section" style="display:none;">
+  <form>
+    <div>
+      <label>Suchwort</label>
+      <input type="search" class="w3-input w3-border" name="keyword" id="search_keyword" requiblue="" type="text">
+    </div>
+    <button type="submit" class="w3-button w3-block w3-padding-large w3-blue w3-margin-bottom" onclick="return submit_search(this)">Suchen</button>
+  </form>
+  <div class="w3-section" id="results" style="display:none;">
   </div>
 </div>
index a610d62..04a4fd7 100644 (file)
@@ -7,6 +7,6 @@
       <label>Suchwort</label>
       <input class="w3-input w3-border" name="keyword" id="search_keyword" requiblue="" type="text">
     </div>
-    <button type="submit" class="w3-button w3-block w3-padding-large w3-blue w3-margin-bottom" onclick="return submit_search()">Suchen</button>
+    <button type="submit" class="w3-button w3-block w3-padding-large w3-blue w3-margin-bottom" clear="true" onclick="return submit_search(this)">Suchen</button>
   </form>
 </div>