Initial commit of kettek MediaServe. Contains basic framework and implementation. FileBrowser module fairly complete, much more to be added, including Playlist module and Player module.

master
kts 2014-05-17 16:52:48 -07:00
commit cf52a6d616
24 changed files with 750 additions and 0 deletions

132
MusicServe.php 100644
View File

@ -0,0 +1,132 @@
<?php
namespace ktk\MusicServe;
session_start();
class MusicServe {
static public $modules = array();
static public $errors = array();
static public $warnings = array();
static private $storage = array();
static function loadModule($name) {
if (file_exists('modules/'.$name.'.php')) {
include_once 'modules/'.$name.'.php';
array_push(self::$modules, $name);
} else {
self::reportError(__CLASS__, __FUNCTION__, "Module ".$name." does not exist!");
}
}
static function checkModule($name) {
return array_search($name, self::$modules);
}
static function initProgram() {
foreach(self::$modules as $module) {
if (method_exists('ktk\\MusicServe\\'.$module, 'onInit')) {
call_user_func('ktk\\MusicServe\\'.$module.'::onInit');
}
}
}
static function processProgram() {
// check for module commands and call them if so
if (isset($_POST['m'])) {
if (isset($_POST['c'])) {
if (method_exists('ktk\\MusicServe\\'.$_POST['m'], $_POST['c'])) {
call_user_func('ktk\\MusicServe\\'.$_POST['m'].'::'.$_POST['c'], $_POST['p1']);
}
}
} else if (isset($_GET['m'])) {
if (isset($_GET['c'])) {
if (method_exists('ktk\\MusicServe\\'.$_GET['m'], $_GET['c'])) {
call_user_func('ktk\\MusicServe\\'.$_GET['m'].'::'.$_GET['c'], $_GET['p1']);
}
}
}
foreach(self::$modules as $module) {
if (method_exists('ktk\\MusicServe\\'.$module, 'onProcess')) {
call_user_func('ktk\\MusicServe\\'.$module.'::onProcess');
}
}
}
static function renderCss() {
echo ' <link rel="stylesheet" href="main.css" type="text/css" media="screen" charset="utf-8">'.PHP_EOL;
foreach(self::$modules as $module) {
if (file_exists('modules/'.$module.'.css')) {
echo ' <link rel="stylesheet" href="modules/'.$module.'.css" type="text/css" media="screen" charset="utf-8">'.PHP_EOL;
}
}
}
static function renderProgram() {
foreach(self::$modules as $module) {
if (method_exists('ktk\\MusicServe\\'.$module, 'onRender')) {
call_user_func('ktk\\MusicServe\\'.$module.'::onRender');
}
}
}
static function closeProgram() {
foreach(self::$modules as $module) {
if (method_exists('ktk\\MusicServe\\'.$module, 'onClose')) {
call_user_func('ktk\\MusicServe\\'.$module.'::onClose');
}
}
}
static function reportError($context, $subcontext, $error) {
if(!self::$errors[$context."::".$subcontext])
self::$errors[$context."::".$subcontext] = array();
array_push(self::$errors[$context."::".$subcontext], $error);
}
static function reportWarning($context, $subcontext, $warning) {
if(!self::$warnings[$context."::".$subcontext])
self::$warnings[$context."::".$subcontext] = array();
array_push(self::$warnings[$context."::".$subcontext], $warning);
}
static function loadConf($file) {
if (file_exists('conf/'.$file.'.ini')) {
return parse_ini_file('conf/'.$file.'.ini');
} else {
self::reportWarning(__CLASS__, __FUNCTION__, "Could not load ".'conf/'.$file.'.ini');
}
return array();
}
static function saveConf($data) {
if ($fp = fopen($data, 'w')) {
self::writeData_r($fp, $data);
fclose($fp);
} else {
MusicServe::reportError(__CLASS__, __FUNCTION__, "Could not write to \"".$data."\", please check your permissions!");
return NULL;
}
}
static function writeData_r($handle, $value) {
foreach ($data as $key=>$value) {
if (is_array($value)) {
fwrite($handle, '['.$key.']'.PHP_EOL);
self::writeData_r($handle, $value);
} else {
fwrite($handle, $key.'='.$value.PHP_EOL);
}
}
}
static function storeData($var, $data) {
$_SESSION[$var] = $data;
}
static function &getData($data) {
if (isset($_SESSION[$data])) {
return $_SESSION[$data];
} else {
return NULL;
}
}
}
?>

View File

@ -0,0 +1 @@
pub = pub

26
index.php 100644
View File

@ -0,0 +1,26 @@
<?php
include_once 'MusicServe.php';
ktk\MusicServe\MusicServe::loadModule("BasicAuth");
ktk\MusicServe\MusicServe::loadModule("ErrorReporter");
ktk\MusicServe\MusicServe::loadModule("FileBrowser");
ktk\MusicServe\MusicServe::loadModule("Playlist");
ktk\MusicServe\MusicServe::loadModule("Player");
ktk\MusicServe\MusicServe::initProgram();
ktk\MusicServe\MusicServe::processProgram();
?>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="content-type" content="application/xhtml+xml; charset=UTF-8" />
<title>ktk MusicServe</title>
<?php ktk\MusicServe\MusicServe::renderCss() ?>
</head>
<body>
<?php ktk\MusicServe\MusicServe::renderProgram() ?>
</body>
</html>
<?php ktk\MusicServe\MusicServe::closeProgram() ?>

6
main.css 100644
View File

@ -0,0 +1,6 @@
html,body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}

1
media 120000
View File

@ -0,0 +1 @@
/export/home/kts/Music

View File

@ -0,0 +1,11 @@
#ktk_MusicServe_BasicAuth {
position: absolute;
bottom:0;
right:0;
float:right;
background-color: #222;
}
#ktk_MusicServe_BasicAuth input {
border: 0;
}

View File

@ -0,0 +1,68 @@
<?php
namespace ktk\MusicServe;
class BasicAuth {
static private $realm = "ktk_MusicServe";
static private $auth_file = "BasicAuth";
static private $creds = array();
static public function onInit() {
self::$creds = MusicServe::loadConf(self::$auth_file);
if (!isset($_SESSION['logged'])) {
self::doAuth();
}
}
static public function onRender() {
echo ' <div id="ktk_MusicServe_BasicAuth">'.PHP_EOL;
echo ' <form action="" method="POST">';
echo ' <input type="hidden" name="m" value="BasicAuth" />';
echo ' <input type="hidden" name="c" value="doLogout" />';
echo ' <input type="submit" name="" value="Exit" />';
echo ' </form>';
echo ' </div>';
}
static public function doLogout() {
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
session_destroy();
session_unset($_SESSION['logged']);
header('HTTP/1.1 401 Unauthorized');
die("Logged out, please <a href=\"".$_SERVER['PHP_SELF']."\">click here</a> to login");
}
}
static public function doAuth() {
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.self::$realm.'",qop="auth",nonce="'.uniqid().'",opaque="'.md5(self::$realm).'"');
die("Canceled - refresh or <a href=\"".$_SERVER['PHP_SELF']."\">click here</a> to try again");
}
if (!($data = self::parseHttpDigest($_SERVER['PHP_AUTH_DIGEST'])) || !isset(self::$creds[$data['username']])) {
header('HTTP/1.1 401 Unauthorized');
die("Incorrect credentials, please <a href=\"".$_SERVER['PHP_SELF']."\">click here</a> to try again");
}
$A1 = md5($data['username'] . ':' . self::$realm . ':' . self::$creds[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if ($data['response'] != $valid_response) {
header('HTTP/1.1 401 Unauthorized');
die("Incorrect credentials, please <a href=\"".$_SERVER['PHP_SELF']."\">click here</a> to try again");
}
$_SESSION['logged'] = TRUE;
}
static public function parseHttpDigest($text) {
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();
$keys = implode('|', array_keys($needed_parts));
preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $text, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}
return $needed_parts ? false : $data;
}
}
?>

24
modules/Clock.php 100644
View File

@ -0,0 +1,24 @@
<?php
namespace ktk\MusicServe;
class Clock {
static private $time = "";
static function onInit() {
if (!MusicServe::getData('time_format')) {
MusicServe::storeData('time_format', 'H:i:s');
}
}
static function onProcess() {
self::$time = date(MusicServe::getData('time_format'));
}
static function onRender() {
echo '<div id="ktk_MusicServe_Clock">'.PHP_EOL;
echo self::$time.PHP_EOL;
echo '<a href="?m=Clock&c=changeTime&p1=g:i:s a">12-hour</a>'.PHP_EOL;
echo '</div>'.PHP_EOL;
}
static function changeTime($format) {
MusicServe::storeData('time_format', $format);
}
}
?>

View File

@ -0,0 +1,21 @@
#ktk_MusicServe_ErrorReporter {
background-color: #633;
color: #FCA;
font-family: console, consolas, terminal;
font-size: 8pt;
}
#ktk_MusicServe_ErrorReporter * {
padding: 0;
margin: 0;
}
#ktk_MusicServe_ErrorReporter h1 {
text-align: center;
color: #FAA;
font-size: 10pt;
}
#ktk_MusicServe_ErrorReporter table {
width: 100%;
}

View File

@ -0,0 +1,24 @@
<?php
namespace ktk\MusicServe;
class ErrorReporter {
static function onInit() {}
static function onRender() {
if (!MusicServe::$errors)
return;
echo '<div id="ktk_MusicServe_ErrorReporter">';
echo '<h1>ERRORS</h1>';
echo '<table>';
echo '<tr><th>context</th><th>error</th></tr>';
foreach (MusicServe::$errors as $context=>$error_context) {
foreach($error_context as $error) {
echo '<tr><td>'.$context.'</td><td>'.$error.'</td></tr>';
}
}
echo '</table>';
echo '</div>';
}
}
?>

View File

@ -0,0 +1,57 @@
#ktk_MusicServe_FileBrowser {
float: left;
overflow: auto;
margin: auto;
width:200px;
display: block;
height:100%;
background-color: #444;
overflow-x: hidden;
overflow-y: scroll;
}
#ktk_MusicServe_FileBrowser * {
margin: 0;
padding: 0;
border: 0;
font-family: console, consolas, terminal;
font-size: 10px;
text-align: left;
}
#ktk_MusicServe_FileBrowser h1 {
display: block;
height: 24px;
overflow: hidden;
color: #CCC;
}
#ktk_MusicServe_FileBrowser table {
width: 100%;
}
#ktk_MusicServe_FileBrowser table,
#ktk_MusicServe_FileBrowser tr,
#ktk_MusicServe_FileBrowser td,
#ktk_MusicServe_FileBrowser input
{
background-color: #444;
color: #CCC;
}
#ktk_MusicServe_FileBrowser th,
#ktk_MusicServe_FileBrowser th input {
min-width: 32px;
background-color:#777;
}
#ktk_MusicServe_FileBrowser td input {
width: 160px;
background-color: #111;
color: #CCC;
}
#ktk_MusicServe_FileBrowser input:hover {
background-color: #777;
cursor: pointer;
}

View File

@ -0,0 +1,168 @@
<?php
namespace ktk\MusicServe;
class FileBrowser {
const ROOT = "media/";
const T_DIR = 1;
const T_FILE = 2;
const F_TYPE = 0;
const F_NAME = 1;
const F_EXT = 2;
static function onInit() {
// set default options if need be
if (!MusicServe::getData('sort_by')) {
MusicServe::storeData('sort_order', SORT_ASC);
MusicServe::storeData('sort_by', "name");
}
if (!MusicServe::getData('cwd')) {
MusicServe::storeData('cwd', FileBrowser::ROOT);
self::getFiles();
self::sortFiles();
}
}
static function onRender() {
$cwd = MusicServe::getData('cwd');
echo ' <div id="ktk_MusicServe_FileBrowser">'.PHP_EOL;
echo ' <h1>',$cwd,'</h1>'.PHP_EOL;
if (MusicServe::checkModule("Playlist")) {
echo ' <form action="" method="POST">';
echo ' <input type="hidden" name="m" value="FileBrowser" />';
echo ' <input type="hidden" name="c" value="openFiles" />';
echo ' <input type="submit" name="" value="add all to playlist" />';
echo ' </form>';
}
echo ' <table>'.PHP_EOL;
echo ' <form action="" method="POST">';
echo ' <input type="hidden" name="m" value="FileBrowser" />';
echo ' <input type="hidden" name="c" value="changeSort" />';
echo ' <tr><th><input type="submit" name="p1" value="name" /></th><th><input type="submit" name="p1" value="ext" /></th></tr>'.PHP_EOL;
echo ' </form>'.PHP_EOL;
echo ' <form action="" method="POST">';
echo ' <input type="hidden" name="m" value="FileBrowser" />';
echo ' <input type="hidden" name="c" value="changeCwd" />';
echo ' <tr><td><input type="submit" name="p1" value="../"></td><td></td></tr>'.PHP_EOL;
foreach(MusicServe::getData('dirs') as $dir) {
echo ' <tr><td><input type="submit" name="p1" value="'.$dir[FileBrowser::F_NAME].'/"></td><td></td></tr>'.PHP_EOL;
}
echo ' </form>'.PHP_EOL;
echo ' <form action="" method="POST">';
echo ' <input type="hidden" name="m" value="FileBrowser" />';
echo ' <input type="hidden" name="c" value="openFile" />';
foreach(MusicServe::getData('files') as $file) {
echo ' <tr><td><input type="submit" name="p1" value="'.$file[FileBrowser::F_NAME].'"/></td><td>'.$file[FileBrowser::F_EXT].'</td></tr>'.PHP_EOL;
}
echo ' </form>'.PHP_EOL;
echo ' </table>'.PHP_EOL;
echo ' </div>'.PHP_EOL;
}
/* COMMAND FUNCTIONS */
static function changeCwd($directory) {
// NOTE: temporarily resolving any ".." to one dir up, rather than all
if (substr_count($directory, "..")) {
if (MusicServe::getData('cwd') == FileBrowser::ROOT) {
return NULL;
}
$directory = dirname(MusicServe::getData('cwd')).'/';
} else {
$directory = MusicServe::getData('cwd').$directory;
}
if (!file_exists($directory)) {
MusicServe::reportError(__CLASS__, __FUNCTION__,
"Cannot change working directory to \"'.$directory.'\", as it does not exist!\n"
);
return NULL;
}
MusicServe::storeData('cwd', $directory);
self::getFiles();
self::sortFiles();
}
static function changeSort($type) {
MusicServe::storeData('sort_by', $type);
if (($sort_order = MusicServe::getData('sort_order')) != SORT_ASC) {
MusicServe::storeData('sort_order', SORT_ASC);
} else {
MusicServe::storeData('sort_order', SORT_DESC);
}
self::sortFiles();
}
static function openFile($file) {
if (MusicServe::checkModule("Playlist")) {
Playlist::addFile(MusicServe::getData('cwd').$file);
}
}
static function openFiles() {
if (MusicServe::checkModule("Playlist")) {
foreach(MusicServe::getData('files') as $file) {
Playlist::addFile(MusicServe::getData('cwd').$file[FileBrowser::F_NAME]);
}
}
}
/* GENERAL FUNCTIONS */
static function getFiles() {
$directory = MusicServe::getData('cwd');
if (!file_exists($directory)) {
MusicServe::reportError(__CLASS__, __FUNCTION__,
"Directory \"'.$directory.'\"does not exist!\n"
);
return NULL;
}
$directory_handle = opendir($directory);
$i = 0;
while (false !== ($filename = readdir($directory_handle))) {
if ($filename[0] != '.' && $filename != "..") {
$files[$i] = array();
if (is_dir($directory.$filename)) {
$files[$i][FileBrowser::F_TYPE] = FileBrowser::T_DIR;
} else {
$files[$i][FileBrowser::F_TYPE] = FileBrowser::T_FILE;
}
$files[$i][FileBrowser::F_NAME] = $filename;
$files[$i][FileBrowser::F_EXT] = pathinfo($filename, PATHINFO_EXTENSION);
$i++;
}
}
MusicServe::storeData('dir_list', $files);
}
/* INTERNAL FUNCTIONS */
static function sortFiles() {
$sort = array();
$sort_by = MusicServe::getData('sort_by');
$sort_order = MusicServe::getData('sort_order');
$i = 0;
if ($sort_by == "ext") {
foreach (MusicServe::getData('dir_list') as $file) {
$sort[$i++] = $file[FileBrowser::F_EXT];
}
} else {
foreach (MusicServe::getData('dir_list') as $file) {
$sort[$i++] = $file[FileBrowser::F_NAME];
}
}
array_multisort($sort, $sort_order, MusicServe::getData('dir_list'));
// Wasteful, but we want directories always on top
$dirs = array();
$files = array();
foreach (MusicServe::getData('dir_list') as $file) {
if ($file[FileBrowser::F_TYPE] == FileBrowser::T_DIR) {
$dirs[] = $file;
} else {
$files[] = $file;
}
}
MusicServe::storeData('files', $files);
MusicServe::storeData('dirs', $dirs);
}
}
?>

View File

@ -0,0 +1,4 @@
#ktk_MusicServe_Player {
background-color: #111;
height: 65%;
}

17
modules/Player.php 100644
View File

@ -0,0 +1,17 @@
<?php
namespace ktk\MusicServe;
class Player {
static public function onInit() {
}
static public function onProcess() {
}
static public function onRender() {
echo '<div id="ktk_MusicServe_Player">',PHP_EOL;
echo "Player goes here";
echo '</div>',PHP_EOL;
}
static public function onClose() {
}
}
?>

View File

@ -0,0 +1,10 @@
#ktk_MusicServe_Playlist * {
font-size: 10px;
}
#ktk_MusicServe_Playlist {
background-color: #444;
color: #BBA;
height: 35%;
overflow-y: auto;
font-size: 12px;
}

View File

@ -0,0 +1,27 @@
<?php
namespace ktk\MusicServe;
class Playlist {
static private $playlist;
static public function onInit() {
if (!MusicServe::getData('playlist')) {
MusicServe::storeData('playlist', array());
}
}
static public function onProcess() {
}
static public function onRender() {
echo ' <div id="ktk_MusicServe_Playlist">',PHP_EOL;
foreach (MusicServe::getData('playlist') as $file) {
echo $file.'<br />';
}
echo ' </div>',PHP_EOL;
}
static public function onClose() {
}
static public function addFile($file) {
MusicServe::getData('playlist')[] = $file;
}
}
?>

View File

@ -0,0 +1,14 @@
<?php
namespace ktk\MusicServe;
class TemplateModule {
static public function onInit() {
}
static public function onProcess() {
}
static public function onRender() {
}
static public function onClose() {
}
}
?>

View File

@ -0,0 +1,5 @@
#ktk_MusicServe_Devel {
position: absolute;
bottom: 0;
right: 0;
}

View File

@ -0,0 +1,26 @@
<?php
namespace ktk\MusicServe;
class Devel {
static public function onRender() {
echo '<div id="ktk_MusicServe_Devel">',PHP_EOL;
echo '<form action="" method="POST">',PHP_EOL;
echo '<input type="hidden" name="m" value="Devel" />',PHP_EOL;
echo '<input type="submit" name="c" value="endSession" />',PHP_EOL;
echo '</form>',PHP_EOL;
echo '</div>',PHP_EOL;
}
static public function endSession() {
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
session_destroy();
header('Location: '.$_SERVER['PHP_SELF']);
die;
}
}
}
?>

View File

@ -0,0 +1,24 @@
<?php
namespace ktk\MusicServe;
class SimpleClock {
static private $time = "";
static function onInit() {
if (!SimpleDB::getData('time_format')) {
SimpleDB::storeData('time_format', 'H:i:s');
}
}
static function onProcess() {
self::$time = date(SimpleDB::getData('time_format'));
}
static function onRender() {
echo '<div id="ktk_MusicServe_Clock">'.PHP_EOL;
echo self::$time.PHP_EOL;
echo '<a href="?m=SimpleClock&c=changeTime&p1=g:i:s a">12-hour</a>'.PHP_EOL;
echo '</div>'.PHP_EOL;
}
static function changeTime($format) {
SimpleDB::storeData('time_format', $format);
}
}
?>

View File

@ -0,0 +1,52 @@
<?php
namespace ktk\MusicServe;
class SimpleDB {
static private $data_file = "sdb.ini";
static private $data = array();
// CORE
static public function onInit() {
if (!file_exists(self::$data_file)) {
if ($fp = fopen(self::$data_file, 'w')) {
fwrite($fp, "");
fclose($fp);
} else {
MusicServe::reportError(__CLASS__, __FUNCTION__, "Could not create \"".self::$data_file."\", please check your permissions!");
}
return NULL;
}
self::$data = parse_ini_file(self::$data_file, true);
}
static public function onClose() {
if ($fp = fopen(self::$data_file, 'w')) {
self::writeData_r($fp, self::$data);
fclose($fp);
} else {
MusicServe::reportError(__CLASS__, __FUNCTION__, "Could not write to \"".self::$data_file."\", please check your permissions!");
return NULL;
}
}
// ACCESS
static public function getData($var) {
if (self::$data[$var]) {
return self::$data[$var];
} else {
return NULL;
}
}
static public function storeData($var, $value) {
self::$data[$var] = $value;
}
// INTERNAL
static private function writeData_r($handle, $data) {
foreach ($data as $key=>$value) {
if (is_array($value)) {
fwrite($handle, '['.$key.']'.PHP_EOL);
self::writeData_r($handle, $value);
} else {
fwrite($handle, $key.'='.$value.PHP_EOL);
}
}
}
}
?>

View File

@ -0,0 +1,7 @@
#ktk_MusicServe_StyledClock {
background-color: #222;
color: #999;
position: fixed;
top:0;
right:0;
}

View File

@ -0,0 +1,24 @@
<?php
namespace ktk\MusicServe;
class StyledClock {
static private $time = "";
static function onInit() {
if (!MusicServe::getData('time_format')) {
MusicServe::storeData('time_format', 'H:i:s');
}
}
static function onProcess() {
self::$time = date(MusicServe::getData('time_format'));
}
static function onRender() {
echo '<div id="ktk_MusicServe_StyledClock">'.PHP_EOL;
echo self::$time.PHP_EOL;
echo '<a href="?m=StyledClock&c=changeTime&p1=g:i:s a">12-hour</a>'.PHP_EOL;
echo '</div>'.PHP_EOL;
}
static function changeTime($format) {
MusicServe::storeData('time_format', $format);
}
}
?>

1
sdb.ini 100644
View File

@ -0,0 +1 @@
time_format=g:i:s a