diff options
author | Paul Arthur <paul.arthur@flowerysong.com> | 2013-01-26 03:00:32 -0500 |
---|---|---|
committer | Paul Arthur <paul.arthur@flowerysong.com> | 2013-01-26 03:38:46 -0500 |
commit | ef4d3660605efc7f1328d4533b0f4bfb6c1107e2 (patch) | |
tree | e4377fb129a899e65aaaf421f8c97098aecaedd5 /lib/class/catalog.class.php | |
parent | 8a750c3e875d590d351c3042570a134fcdf03e5d (diff) | |
download | ampache-ef4d3660605efc7f1328d4533b0f4bfb6c1107e2.tar.gz ampache-ef4d3660605efc7f1328d4533b0f4bfb6c1107e2.tar.bz2 ampache-ef4d3660605efc7f1328d4533b0f4bfb6c1107e2.zip |
Cosmetics: death to tabs
The refactoring I've been doing has reminded me of my strong preference
for spaces, and I feel inclined to impose my will on the tree.
Diffstat (limited to 'lib/class/catalog.class.php')
-rw-r--r-- | lib/class/catalog.class.php | 3968 |
1 files changed, 1984 insertions, 1984 deletions
diff --git a/lib/class/catalog.class.php b/lib/class/catalog.class.php index 3833d329..df5229a4 100644 --- a/lib/class/catalog.class.php +++ b/lib/class/catalog.class.php @@ -1,5 +1,5 @@ <?php -/* vim:set tabstop=8 softtabstop=8 shiftwidth=8 noexpandtab: */ +/* vim:set softtabstop=4 shiftwidth=4 expandtab: */ /** * * LICENSE: GNU General Public License, version 2 (GPLv2) @@ -29,2079 +29,2079 @@ */ class Catalog extends database_object { - public $name; - public $last_update; - public $last_add; - public $last_clean; - public $key; - public $rename_pattern; - public $sort_pattern; - public $catalog_type; - public $path; + public $name; + public $last_update; + public $last_add; + public $last_clean; + public $key; + public $rename_pattern; + public $sort_pattern; + public $catalog_type; + public $path; - /* This is a private var that's used during catalog builds */ - private $_playlists = array(); + /* This is a private var that's used during catalog builds */ + private $_playlists = array(); - // Cache all files in catalog for quick lookup during add - private $_filecache = array(); + // Cache all files in catalog for quick lookup during add + private $_filecache = array(); - // Used in functions - private static $albums = array(); - private static $artists = array(); - private static $tags = array(); - private static $_art_albums = array(); + // Used in functions + private static $albums = array(); + private static $artists = array(); + private static $tags = array(); + private static $_art_albums = array(); - /** - * Constructor - * Catalog class constructor, pulls catalog information - * $catalog_id The ID of the catalog you want to build information from - */ - public function __construct($catalog_id = '') { - - if (!$catalog_id) { return false; } - - /* Assign id for use in get_info() */ - $this->id = intval($catalog_id); + /** + * Constructor + * Catalog class constructor, pulls catalog information + * $catalog_id The ID of the catalog you want to build information from + */ + public function __construct($catalog_id = '') { + + if (!$catalog_id) { return false; } + + /* Assign id for use in get_info() */ + $this->id = intval($catalog_id); - /* Get the information from the db */ - $info = $this->get_info($catalog_id); + /* Get the information from the db */ + $info = $this->get_info($catalog_id); - foreach ($info as $key=>$value) { - $this->$key = $value; - } + foreach ($info as $key=>$value) { + $this->$key = $value; + } - } //constructor + } //constructor - /** - * _create_filecache - * This poplates an array (filecache) on this object from the database - * it is used to speed up the add process - */ - private function _create_filecache() { + /** + * _create_filecache + * This poplates an array (filecache) on this object from the database + * it is used to speed up the add process + */ + private function _create_filecache() { - if (count($this->_filecache) == 0) { - $catalog_id = Dba::escape($this->id); - // Get _EVERYTHING_ - $sql = "SELECT `id`,`file` FROM `song` WHERE `catalog`='$catalog_id'"; - $db_results = Dba::read($sql); - - // Populate the filecache - while ($results = Dba::fetch_assoc($db_results)) { - $this->_filecache[strtolower($results['file'])] = $results['id']; - } + if (count($this->_filecache) == 0) { + $catalog_id = Dba::escape($this->id); + // Get _EVERYTHING_ + $sql = "SELECT `id`,`file` FROM `song` WHERE `catalog`='$catalog_id'"; + $db_results = Dba::read($sql); + + // Populate the filecache + while ($results = Dba::fetch_assoc($db_results)) { + $this->_filecache[strtolower($results['file'])] = $results['id']; + } - $sql = "SELECT `id`,`file` FROM `video` WHERE `catalog`='$catalog_id'"; - $db_results = Dba::read($sql); + $sql = "SELECT `id`,`file` FROM `video` WHERE `catalog`='$catalog_id'"; + $db_results = Dba::read($sql); - while ($results = Dba::fetch_assoc($db_results)) { - $this->_filecache[strtolower($results['file'])] = 'v_' . $results['id']; - } - } // end if empty filecache + while ($results = Dba::fetch_assoc($db_results)) { + $this->_filecache[strtolower($results['file'])] = 'v_' . $results['id']; + } + } // end if empty filecache - return true; + return true; - } // _create_filecache + } // _create_filecache - /** - * get_from_path - * Try to figure out which catalog path most closely resembles this one - * This is useful when creating a new catalog to make sure we're not - * doubling up here. - */ - public static function get_from_path($path) { + /** + * get_from_path + * Try to figure out which catalog path most closely resembles this one + * This is useful when creating a new catalog to make sure we're not + * doubling up here. + */ + public static function get_from_path($path) { - // First pull a list of all of the paths for the different catalogs - $sql = "SELECT `id`,`path` FROM `catalog` WHERE `catalog_type`='local'"; - $db_results = Dba::read($sql); + // First pull a list of all of the paths for the different catalogs + $sql = "SELECT `id`,`path` FROM `catalog` WHERE `catalog_type`='local'"; + $db_results = Dba::read($sql); - $catalog_paths = array(); - $component_path = $path; + $catalog_paths = array(); + $component_path = $path; - while ($row = Dba::fetch_assoc($db_results)) { - $catalog_paths[$row['path']] = $row['id']; - } + while ($row = Dba::fetch_assoc($db_results)) { + $catalog_paths[$row['path']] = $row['id']; + } - // Break it down into its component parts and start looking for a catalog - do { - if ($catalog_paths[$component_path]) { - return $catalog_paths[$component_path]; - } + // Break it down into its component parts and start looking for a catalog + do { + if ($catalog_paths[$component_path]) { + return $catalog_paths[$component_path]; + } - // Keep going until the path stops changing - $old_path = $component_path; - $component_path = realpath($component_path . '/../'); + // Keep going until the path stops changing + $old_path = $component_path; + $component_path = realpath($component_path . '/../'); - } while (strcmp($component_path,$old_path) != 0); + } while (strcmp($component_path,$old_path) != 0); - return false; + return false; - } // get_from_path + } // get_from_path - /** - * format - * This makes the object human readable - */ - public function format() { + /** + * format + * This makes the object human readable + */ + public function format() { - $this->f_name = UI::truncate($this->name,Config::get('ellipse_threshold_title')); - $this->f_name_link = '<a href="' . Config::get('web_path') . '/admin/catalog.php?action=show_customize_catalog&catalog_id=' . $this->id . '" title="' . scrub_out($this->name) . '">' . scrub_out($this->f_name) . '</a>'; - $this->f_path = UI::truncate($this->path,Config::get('ellipse_threshold_title')); - $this->f_update = $this->last_update ? date('d/m/Y h:i',$this->last_update) : T_('Never'); - $this->f_add = $this->last_add ? date('d/m/Y h:i',$this->last_add) : T_('Never'); - $this->f_clean = $this->last_clean ? date('d/m/Y h:i',$this->last_clean) : T_('Never'); + $this->f_name = UI::truncate($this->name,Config::get('ellipse_threshold_title')); + $this->f_name_link = '<a href="' . Config::get('web_path') . '/admin/catalog.php?action=show_customize_catalog&catalog_id=' . $this->id . '" title="' . scrub_out($this->name) . '">' . scrub_out($this->f_name) . '</a>'; + $this->f_path = UI::truncate($this->path,Config::get('ellipse_threshold_title')); + $this->f_update = $this->last_update ? date('d/m/Y h:i',$this->last_update) : T_('Never'); + $this->f_add = $this->last_add ? date('d/m/Y h:i',$this->last_add) : T_('Never'); + $this->f_clean = $this->last_clean ? date('d/m/Y h:i',$this->last_clean) : T_('Never'); - } // format + } // format - /** - * get_catalogs - * Pull all the current catalogs and return an array of ids - * of what you find - */ - public static function get_catalogs() { + /** + * get_catalogs + * Pull all the current catalogs and return an array of ids + * of what you find + */ + public static function get_catalogs() { - $sql = "SELECT `id` FROM `catalog` ORDER BY `name`"; - $db_results = Dba::read($sql); + $sql = "SELECT `id` FROM `catalog` ORDER BY `name`"; + $db_results = Dba::read($sql); - $results = array(); + $results = array(); - while ($row = Dba::fetch_assoc($db_results)) { - $results[] = $row['id']; - } + while ($row = Dba::fetch_assoc($db_results)) { + $results[] = $row['id']; + } - return $results; + return $results; - } // get_catalogs - - /** - * get_stats - * This returns an hash with the #'s for the different - * objects that are associated with this catalog. This is used - * to build the stats box, it also calculates time. - */ - public static function get_stats($catalog_id = null) { - - $results = self::count_songs($catalog_id); - $results = array_merge(self::count_users($catalog_id), $results); - $results['tags'] = self::count_tags(); - $results = array_merge(self::count_videos($catalog_id), $results); + } // get_catalogs + + /** + * get_stats + * This returns an hash with the #'s for the different + * objects that are associated with this catalog. This is used + * to build the stats box, it also calculates time. + */ + public static function get_stats($catalog_id = null) { + + $results = self::count_songs($catalog_id); + $results = array_merge(self::count_users($catalog_id), $results); + $results['tags'] = self::count_tags(); + $results = array_merge(self::count_videos($catalog_id), $results); - $hours = floor($results['time'] / 3600); + $hours = floor($results['time'] / 3600); - $results['formatted_size'] = UI::format_bytes($results['size']); + $results['formatted_size'] = UI::format_bytes($results['size']); - $days = floor($hours / 24); - $hours = $hours % 24; + $days = floor($hours / 24); + $hours = $hours % 24; - $time_text = "$days "; - $time_text .= T_ngettext('day','days',$days); - $time_text .= ", $hours "; - $time_text .= T_ngettext('hour','hours',$hours); + $time_text = "$days "; + $time_text .= T_ngettext('day','days',$days); + $time_text .= ", $hours "; + $time_text .= T_ngettext('hour','hours',$hours); - $results['time_text'] = $time_text; + $results['time_text'] = $time_text; - return $results; + return $results; - } // get_stats - - /** - * create - * This creates a new catalog entry and then returns the insert id - * it checks to make sure this path is not already used before creating - * the catalog - */ - public static function create($data) { + } // get_stats + + /** + * create + * This creates a new catalog entry and then returns the insert id + * it checks to make sure this path is not already used before creating + * the catalog + */ + public static function create($data) { - // Clean up the path just in case - $data['path'] = rtrim(rtrim(trim($data['path']),'/'),'\\'); + // Clean up the path just in case + $data['path'] = rtrim(rtrim(trim($data['path']),'/'),'\\'); - $path = Dba::escape($data['path']); + $path = Dba::escape($data['path']); - // Make sure the path is readable/exists - if ($data['type'] == 'local') { - $handle = opendir($path); - if ($handle === false) { - Error::add('general', sprintf(T_('Error: %s is not readable or does not exist'), scrub_out($data['path']))); - return false; - } - closedir($handle); - } + // Make sure the path is readable/exists + if ($data['type'] == 'local') { + $handle = opendir($path); + if ($handle === false) { + Error::add('general', sprintf(T_('Error: %s is not readable or does not exist'), scrub_out($data['path']))); + return false; + } + closedir($handle); + } - // Make sure this path isn't already in use by an existing catalog - $sql = "SELECT `id` FROM `catalog` WHERE `path`='$path'"; - $db_results = Dba::read($sql); + // Make sure this path isn't already in use by an existing catalog + $sql = "SELECT `id` FROM `catalog` WHERE `path`='$path'"; + $db_results = Dba::read($sql); - if (Dba::num_rows($db_results)) { - Error::add('general', sprintf(T_('Error: Catalog with %s already exists'), $path)); - return false; - } + if (Dba::num_rows($db_results)) { + Error::add('general', sprintf(T_('Error: Catalog with %s already exists'), $path)); + return false; + } - $name = Dba::escape($data['name']); - $catalog_type = Dba::escape($data['type']); - $rename_pattern = Dba::escape($data['rename_pattern']); - $sort_pattern = Dba::escape($data['sort_pattern']); - $gather_types = 'NULL'; - $remote_username = 'NULL'; - $remote_password = 'NULL'; + $name = Dba::escape($data['name']); + $catalog_type = Dba::escape($data['type']); + $rename_pattern = Dba::escape($data['rename_pattern']); + $sort_pattern = Dba::escape($data['sort_pattern']); + $gather_types = 'NULL'; + $remote_username = 'NULL'; + $remote_password = 'NULL'; - // Don't save these if it isn't a remote catalog - if ($catalog_type == 'remote') { - $remote_username = "'" . Dba::escape($data['remote_username']) . "'"; - $remote_password = "'" . Dba::escape($data['remote_password']) . "'"; - } + // Don't save these if it isn't a remote catalog + if ($catalog_type == 'remote') { + $remote_username = "'" . Dba::escape($data['remote_username']) . "'"; + $remote_password = "'" . Dba::escape($data['remote_password']) . "'"; + } - // Ok we're good to go ahead and insert this record - $sql = "INSERT INTO `catalog` (`name`,`path`,`catalog_type`,`remote_username`,`remote_password`,`rename_pattern`,`sort_pattern`,`gather_types`) " . - "VALUES ('$name','$path','$catalog_type',$remote_username,$remote_password,'$rename_pattern','$sort_pattern',$gather_types)"; - $db_results = Dba::write($sql); + // Ok we're good to go ahead and insert this record + $sql = "INSERT INTO `catalog` (`name`,`path`,`catalog_type`,`remote_username`,`remote_password`,`rename_pattern`,`sort_pattern`,`gather_types`) " . + "VALUES ('$name','$path','$catalog_type',$remote_username,$remote_password,'$rename_pattern','$sort_pattern',$gather_types)"; + $db_results = Dba::write($sql); - $insert_id = Dba::insert_id(); + $insert_id = Dba::insert_id(); - if (!$insert_id) { - Error::add('general', T_('Catalog Insert Failed check debug logs')); - debug_event('catalog','SQL Failed:' . $sql,'3'); - return false; - } + if (!$insert_id) { + Error::add('general', T_('Catalog Insert Failed check debug logs')); + debug_event('catalog','SQL Failed:' . $sql,'3'); + return false; + } - return $insert_id; + return $insert_id; - } // create + } // create - /** - * run_add - * This runs the add to catalog function - * it includes the javascript refresh stuff and then starts rolling - * throught the path for this catalog - */ - public function run_add($options) { - - if ($this->catalog_type == 'remote') { - UI::show_box_top(T_('Running Remote Sync') . '. . .'); - $this->get_remote_catalog($type=0); - UI::show_box_bottom(); - return true; - } + /** + * run_add + * This runs the add to catalog function + * it includes the javascript refresh stuff and then starts rolling + * throught the path for this catalog + */ + public function run_add($options) { + + if ($this->catalog_type == 'remote') { + UI::show_box_top(T_('Running Remote Sync') . '. . .'); + $this->get_remote_catalog($type=0); + UI::show_box_bottom(); + return true; + } - // Catalog Add start - $start_time = time(); + // Catalog Add start + $start_time = time(); - require Config::get('prefix') . '/templates/show_adds_catalog.inc.php'; - flush(); - - // Prevent the script from timing out and flush what we've got - set_time_limit(0); - - $this->add_files($this->path,$options); - - // If they have checked the box then go ahead and gather the art - if ($options['gather_art']) { - $catalog_id = $this->id; - require Config::get('prefix') . '/templates/show_gather_art.inc.php'; - flush(); - $this->get_art('',1); - } - - if ($options['parse_m3u'] AND count($this->_playlists)) { - foreach ($this->_playlists as $playlist_file) { - $result = $this->import_m3u($playlist_file); - } - } // if we need to do some m3u-age - - return true; - - } // run_add - - /** - * count_videos - * This returns the current # of video files we've got in the db - */ - public static function count_videos($catalog_id = null) { - - $catalog_search = $catalog_id ? "WHERE `catalog`='" . Dba::escape($catalog_id) . "'" : ''; - - $sql = 'SELECT COUNT(`id`) AS `videos` FROM `video` '; - if ($catalog_id) { - $sql .= "WHERE `catalog`='" . Dba::escape($catalog_id) . "'"; - } - $db_results = Dba::read($sql); - - $row = Dba::fetch_assoc($db_results); - - return $row; - - } // count_videos - - /** - * count_tags - * This returns the current # of unique tags that exist in the database - */ - public static function count_tags($catalog_id = null) { - - // FIXME: Ignores catalog_id - $sql = "SELECT COUNT(`id`) FROM `tag`"; - $db_results = Dba::read($sql); - - $info = Dba::fetch_row($db_results); - - return $info['0']; - - } // count_tags - - /** - * count_songs - * This returns the current # of songs, albums, artists - * in this catalog - */ - public static function count_songs($catalog_id = null) { - - $where_sql = $catalog_id ? "WHERE `catalog`='" . Dba::escape($catalog_id) . "'" : ''; - - $sql = "SELECT COUNT(`id`),SUM(`time`),SUM(`size`) FROM `song` $where_sql"; - $db_results = Dba::read($sql); - $data = Dba::fetch_row($db_results); - $songs = $data['0']; - $time = $data['1']; - $size = $data['2']; - - $sql = "SELECT COUNT(DISTINCT(`album`)) FROM `song` $where_sql"; - $db_results = Dba::read($sql); - $data = Dba::fetch_row($db_results); - $albums = $data['0']; - - $sql = "SELECT COUNT(DISTINCT(`artist`)) FROM `song` $where_sql"; - $db_results = Dba::read($sql); - $data = Dba::fetch_row($db_results); - $artists = $data['0']; - - $results['songs'] = $songs; - $results['albums'] = $albums; - $results['artists'] = $artists; - $results['size'] = $size; - $results['time'] = $time; - - return $results; - - } // count_songs - - /** - * count_users - * This returns the total number of users in the ampache instance - */ - public static function count_users($catalog_id = null) { - - // Count total users - $sql = "SELECT COUNT(`id`) FROM `user`"; - $db_results = Dba::read($sql); - $data = Dba::fetch_row($db_results); - $results['users'] = $data['0']; - - // Get the connected users - $time = time(); - $last_seen_time = $time - 1200; - $sql = 'SELECT COUNT(DISTINCT `session`.`username`) ' . - 'FROM `session` INNER JOIN `user` ' . - 'ON `session`.`username` = `user`.`username` ' . - "WHERE `session`.`expire` > '$time' " . - "AND `user`.`last_seen` > '$last_seen_time'"; - $db_results = Dba::read($sql); - $data = Dba::fetch_row($db_results); - - $results['connected'] = $data['0']; - - return $results; - - } // count_users - - /** - * add_files - * Recurses through $this->path and pulls out all mp3s and returns the - * full path in an array. Passes gather_type to determine if we need to - * check id3 information against the db. - */ - public function add_files($path, $options) { - - // Profile the memory a bit - debug_event('Memory', UI::format_bytes(memory_get_usage(true)), 5); - - // See if we want a non-root path for the add - if (isset($options['subdirectory'])) { - $path = $options['subdirectory']; - unset($options['subdirectory']); - } - - // Correctly detect the slash we need to use here - if (strpos($path, '/') !== false) { - $slash_type = '/'; - } - else { - $slash_type = '\\'; - } - - /* Open up the directory */ - $handle = opendir($path); - - if (!is_resource($handle)) { - debug_event('read', "Unable to open $path", 5,'ampache-catalog'); - Error::add('catalog_add', sprintf(T_('Error: Unable to open %s'), $path)); - return false; - } - - /* Change the dir so is_dir works correctly */ - if (!chdir($path)) { - debug_event('read', "Unable to chdir $path", 2,'ampache-catalog'); - Error::add('catalog_add', sprintf(T_('Error: Unable to change to directory %s'), $path)); - return false; - } - - // Ensure that we've got our cache - $this->_create_filecache(); - - debug_event('Memory', UI::format_bytes(memory_get_usage(true)), 5); - - /* Recurse through this dir and create the files array */ - while ( false !== ( $file = readdir($handle) ) ) { - - /* Skip to next if we've got . or .. */ - if (substr($file,0,1) == '.') { continue; } - - debug_event('read',"Starting work on $file inside $path",'5','ampache-catalog'); - debug_event('Memory', UI::format_bytes(memory_get_usage(true)), 5); - - /* Create the new path */ - $full_file = $path.$slash_type.$file; - - /* First thing first, check if file is already in catalog. - * This check is very quick, so it should be performed before any other checks to save time - */ - if (isset($this->_filecache[strtolower($full_file)])) { - continue; - } - - // Incase this is the second time through clear this variable - // if it was set the day before - unset($failed_check); - - if (Config::get('no_symlinks')) { - if (is_link($full_file)) { - debug_event('read',"Skipping Symbolic Link $path",'5','ampache-catalog'); - continue; - } - } - - /* If it's a dir run this function again! */ - if (is_dir($full_file)) { - $this->add_files($full_file,$options); - - /* Change the dir so is_dir works correctly */ - if (!chdir($path)) { - debug_event('read',"Unable to chdir $path",'2','ampache-catalog'); - Error::add('catalog_add', sprintf(T_('Error: Unable to change to directory %s'), $path)); - } - - /* Skip to the next file */ - continue; - } //it's a directory - - /* If it's not a dir let's roll with it - * next we need to build the pattern that we will use - * to detect if it's an audio file - */ - $pattern = "/\.(" . Config::get('catalog_file_pattern'); - if ($options['parse_m3u']) { - $pattern .= "|m3u)$/i"; - } - else { - $pattern .= ")$/i"; - } - - $is_audio_file = preg_match($pattern,$file); - - // Define the Video file pattern - if (!$is_audio_file AND Config::get('catalog_video_pattern')) { - $video_pattern = "/\.(" . Config::get('catalog_video_pattern') . ")$/i"; - $is_video_file = preg_match($video_pattern,$file); - } - - /* see if this is a valid audio file or playlist file */ - if ($is_audio_file OR $is_video_file) { - - /* Now that we're sure its a file get filesize */ - $file_size = filesize($full_file); - - if (!$file_size) { - debug_event('read',"Unable to get filesize for $full_file",'2','ampache-catalog'); - /* HINT: FullFile */ - Error::add('catalog_add', sprintf(T_('Error: Unable to get filesize for %s'), $full_file)); - } // file_size check - - if (!is_readable($full_file)) { - // not readable, warn user - debug_event('read',"$full_file is not readable by ampache",'2','ampache-catalog'); - /* HINT: FullFile */ - Error::add('catalog_add', sprintf(T_('%s is not readable by ampache'), $full_file)); - continue; - } - - // Check to make sure the filename is of the expected charset - if (function_exists('iconv')) { - if (strcmp($full_file,iconv(Config::get('site_charset'),Config::get('site_charset'),$full_file)) != '0') { - debug_event('read',$full_file . ' has non-' . Config::get('site_charset') . ' characters and can not be indexed, converted filename:' . iconv(Config::get('site_charset'),Config::get('site_charset'),$full_file),'1'); - /* HINT: FullFile */ - Error::add('catalog_add', sprintf(T_('%s does not match site charset'), $full_file)); - continue; - } - } // end if iconv - - if ($options['parse_m3u'] AND substr($file,-3,3) == 'm3u') { - $this->_playlists[] = $full_file; - } // if it's an m3u - - else { - if ($is_audio_file) { $this->insert_local_song($full_file,$file_size); } - else { $this->insert_local_video($full_file,$file_size); } - - $this->count++; - $file = str_replace(array('(',')','\''),'',$full_file); - if(UI::check_ticker()) { - UI::update_text('add_count_' . $this->id, $this->count); - UI::update_text('add_dir_' . $this->id, scrub_out($file)); - } // update our current state - - } // if it's not an m3u - - } //if it matches the pattern - else { - debug_event('read',"$full_file ignored, non audio file or 0 bytes",'5','ampache-catalog'); - } // else not an audio file - - } // end while reading directory - - debug_event('closedir',"Finished reading $path closing handle",'5','ampache-catalog'); - - // This should only happen on the last run - if ($path == $this->path) { - UI::update_text('add_count_' . $this->id, $this->count); - UI::update_text('add_dir_' . $this->id, scrub_out($file)); - } - - - /* Close the dir handle */ - @closedir($handle); - - } // add_files - - /** - * get_album_ids - * This returns an array of ids of albums that have songs in this - * catalog - */ - public function get_album_ids() { - - $id = Dba::escape($this->id); - $results = array(); - - $sql = "SELECT DISTINCT(`song`.`album`) FROM `song` WHERE `song`.`catalog`='$id'"; - $db_results = Dba::read($sql); - - while ($r = Dba::fetch_assoc($db_results)) { - $results[] = $r['album']; - } - - return $results; - - } // get_album_ids - - /** - * get_art - * This runs through all of the needs art albums and trys - * to find the art for them from the mp3s - */ - public function get_art($catalog_id = null, $all = false) { - - // Make sure they've actually got methods - $art_order = Config::get('art_order'); - if (!count($art_order)) { - debug_event('gather_art', 'art_order not set, Catalog::get_art aborting', 3); - return true; - } - - // Prevent the script from timing out - set_time_limit(0); - - // If not passed use $this - $catalog_id = $catalog_id ? $catalog_id : $this->id; - - if ($all) { - $albums = $this->get_album_ids(); - } - else { - $albums = array_keys(self::$_art_albums); - } - - // Run through them an get the art! - foreach ($albums as $album_id) { - - // Create the object - $art = new Art($album_id, 'album'); - $album = new Album($album_id); - // We're going to need the name here - $album->format(); - - debug_event('gather_art', 'Gathering art for ' . $album->name, 5); - - // Define the options we want to use for the find art function - $options = array( - 'album_name' => $album->full_name, - 'artist' => $album->artist_name, - 'keyword' => $album->artist_name . ' ' . $album->full_name - ); + require Config::get('prefix') . '/templates/show_adds_catalog.inc.php'; + flush(); + + // Prevent the script from timing out and flush what we've got + set_time_limit(0); + + $this->add_files($this->path,$options); + + // If they have checked the box then go ahead and gather the art + if ($options['gather_art']) { + $catalog_id = $this->id; + require Config::get('prefix') . '/templates/show_gather_art.inc.php'; + flush(); + $this->get_art('',1); + } + + if ($options['parse_m3u'] AND count($this->_playlists)) { + foreach ($this->_playlists as $playlist_file) { + $result = $this->import_m3u($playlist_file); + } + } // if we need to do some m3u-age + + return true; + + } // run_add + + /** + * count_videos + * This returns the current # of video files we've got in the db + */ + public static function count_videos($catalog_id = null) { + + $catalog_search = $catalog_id ? "WHERE `catalog`='" . Dba::escape($catalog_id) . "'" : ''; + + $sql = 'SELECT COUNT(`id`) AS `videos` FROM `video` '; + if ($catalog_id) { + $sql .= "WHERE `catalog`='" . Dba::escape($catalog_id) . "'"; + } + $db_results = Dba::read($sql); + + $row = Dba::fetch_assoc($db_results); + + return $row; + + } // count_videos + + /** + * count_tags + * This returns the current # of unique tags that exist in the database + */ + public static function count_tags($catalog_id = null) { + + // FIXME: Ignores catalog_id + $sql = "SELECT COUNT(`id`) FROM `tag`"; + $db_results = Dba::read($sql); + + $info = Dba::fetch_row($db_results); + + return $info['0']; + + } // count_tags + + /** + * count_songs + * This returns the current # of songs, albums, artists + * in this catalog + */ + public static function count_songs($catalog_id = null) { + + $where_sql = $catalog_id ? "WHERE `catalog`='" . Dba::escape($catalog_id) . "'" : ''; + + $sql = "SELECT COUNT(`id`),SUM(`time`),SUM(`size`) FROM `song` $where_sql"; + $db_results = Dba::read($sql); + $data = Dba::fetch_row($db_results); + $songs = $data['0']; + $time = $data['1']; + $size = $data['2']; + + $sql = "SELECT COUNT(DISTINCT(`album`)) FROM `song` $where_sql"; + $db_results = Dba::read($sql); + $data = Dba::fetch_row($db_results); + $albums = $data['0']; + + $sql = "SELECT COUNT(DISTINCT(`artist`)) FROM `song` $where_sql"; + $db_results = Dba::read($sql); + $data = Dba::fetch_row($db_results); + $artists = $data['0']; + + $results['songs'] = $songs; + $results['albums'] = $albums; + $results['artists'] = $artists; + $results['size'] = $size; + $results['time'] = $time; + + return $results; + + } // count_songs + + /** + * count_users + * This returns the total number of users in the ampache instance + */ + public static function count_users($catalog_id = null) { + + // Count total users + $sql = "SELECT COUNT(`id`) FROM `user`"; + $db_results = Dba::read($sql); + $data = Dba::fetch_row($db_results); + $results['users'] = $data['0']; + + // Get the connected users + $time = time(); + $last_seen_time = $time - 1200; + $sql = 'SELECT COUNT(DISTINCT `session`.`username`) ' . + 'FROM `session` INNER JOIN `user` ' . + 'ON `session`.`username` = `user`.`username` ' . + "WHERE `session`.`expire` > '$time' " . + "AND `user`.`last_seen` > '$last_seen_time'"; + $db_results = Dba::read($sql); + $data = Dba::fetch_row($db_results); + + $results['connected'] = $data['0']; + + return $results; + + } // count_users + + /** + * add_files + * Recurses through $this->path and pulls out all mp3s and returns the + * full path in an array. Passes gather_type to determine if we need to + * check id3 information against the db. + */ + public function add_files($path, $options) { + + // Profile the memory a bit + debug_event('Memory', UI::format_bytes(memory_get_usage(true)), 5); + + // See if we want a non-root path for the add + if (isset($options['subdirectory'])) { + $path = $options['subdirectory']; + unset($options['subdirectory']); + } + + // Correctly detect the slash we need to use here + if (strpos($path, '/') !== false) { + $slash_type = '/'; + } + else { + $slash_type = '\\'; + } + + /* Open up the directory */ + $handle = opendir($path); + + if (!is_resource($handle)) { + debug_event('read', "Unable to open $path", 5,'ampache-catalog'); + Error::add('catalog_add', sprintf(T_('Error: Unable to open %s'), $path)); + return false; + } + + /* Change the dir so is_dir works correctly */ + if (!chdir($path)) { + debug_event('read', "Unable to chdir $path", 2,'ampache-catalog'); + Error::add('catalog_add', sprintf(T_('Error: Unable to change to directory %s'), $path)); + return false; + } + + // Ensure that we've got our cache + $this->_create_filecache(); + + debug_event('Memory', UI::format_bytes(memory_get_usage(true)), 5); + + /* Recurse through this dir and create the files array */ + while ( false !== ( $file = readdir($handle) ) ) { + + /* Skip to next if we've got . or .. */ + if (substr($file,0,1) == '.') { continue; } + + debug_event('read',"Starting work on $file inside $path",'5','ampache-catalog'); + debug_event('Memory', UI::format_bytes(memory_get_usage(true)), 5); + + /* Create the new path */ + $full_file = $path.$slash_type.$file; + + /* First thing first, check if file is already in catalog. + * This check is very quick, so it should be performed before any other checks to save time + */ + if (isset($this->_filecache[strtolower($full_file)])) { + continue; + } + + // Incase this is the second time through clear this variable + // if it was set the day before + unset($failed_check); + + if (Config::get('no_symlinks')) { + if (is_link($full_file)) { + debug_event('read',"Skipping Symbolic Link $path",'5','ampache-catalog'); + continue; + } + } + + /* If it's a dir run this function again! */ + if (is_dir($full_file)) { + $this->add_files($full_file,$options); + + /* Change the dir so is_dir works correctly */ + if (!chdir($path)) { + debug_event('read',"Unable to chdir $path",'2','ampache-catalog'); + Error::add('catalog_add', sprintf(T_('Error: Unable to change to directory %s'), $path)); + } + + /* Skip to the next file */ + continue; + } //it's a directory + + /* If it's not a dir let's roll with it + * next we need to build the pattern that we will use + * to detect if it's an audio file + */ + $pattern = "/\.(" . Config::get('catalog_file_pattern'); + if ($options['parse_m3u']) { + $pattern .= "|m3u)$/i"; + } + else { + $pattern .= ")$/i"; + } + + $is_audio_file = preg_match($pattern,$file); + + // Define the Video file pattern + if (!$is_audio_file AND Config::get('catalog_video_pattern')) { + $video_pattern = "/\.(" . Config::get('catalog_video_pattern') . ")$/i"; + $is_video_file = preg_match($video_pattern,$file); + } + + /* see if this is a valid audio file or playlist file */ + if ($is_audio_file OR $is_video_file) { + + /* Now that we're sure its a file get filesize */ + $file_size = filesize($full_file); + + if (!$file_size) { + debug_event('read',"Unable to get filesize for $full_file",'2','ampache-catalog'); + /* HINT: FullFile */ + Error::add('catalog_add', sprintf(T_('Error: Unable to get filesize for %s'), $full_file)); + } // file_size check + + if (!is_readable($full_file)) { + // not readable, warn user + debug_event('read',"$full_file is not readable by ampache",'2','ampache-catalog'); + /* HINT: FullFile */ + Error::add('catalog_add', sprintf(T_('%s is not readable by ampache'), $full_file)); + continue; + } + + // Check to make sure the filename is of the expected charset + if (function_exists('iconv')) { + if (strcmp($full_file,iconv(Config::get('site_charset'),Config::get('site_charset'),$full_file)) != '0') { + debug_event('read',$full_file . ' has non-' . Config::get('site_charset') . ' characters and can not be indexed, converted filename:' . iconv(Config::get('site_charset'),Config::get('site_charset'),$full_file),'1'); + /* HINT: FullFile */ + Error::add('catalog_add', sprintf(T_('%s does not match site charset'), $full_file)); + continue; + } + } // end if iconv + + if ($options['parse_m3u'] AND substr($file,-3,3) == 'm3u') { + $this->_playlists[] = $full_file; + } // if it's an m3u + + else { + if ($is_audio_file) { $this->insert_local_song($full_file,$file_size); } + else { $this->insert_local_video($full_file,$file_size); } + + $this->count++; + $file = str_replace(array('(',')','\''),'',$full_file); + if(UI::check_ticker()) { + UI::update_text('add_count_' . $this->id, $this->count); + UI::update_text('add_dir_' . $this->id, scrub_out($file)); + } // update our current state + + } // if it's not an m3u + + } //if it matches the pattern + else { + debug_event('read',"$full_file ignored, non audio file or 0 bytes",'5','ampache-catalog'); + } // else not an audio file + + } // end while reading directory + + debug_event('closedir',"Finished reading $path closing handle",'5','ampache-catalog'); + + // This should only happen on the last run + if ($path == $this->path) { + UI::update_text('add_count_' . $this->id, $this->count); + UI::update_text('add_dir_' . $this->id, scrub_out($file)); + } + + + /* Close the dir handle */ + @closedir($handle); + + } // add_files + + /** + * get_album_ids + * This returns an array of ids of albums that have songs in this + * catalog + */ + public function get_album_ids() { + + $id = Dba::escape($this->id); + $results = array(); + + $sql = "SELECT DISTINCT(`song`.`album`) FROM `song` WHERE `song`.`catalog`='$id'"; + $db_results = Dba::read($sql); + + while ($r = Dba::fetch_assoc($db_results)) { + $results[] = $r['album']; + } + + return $results; + + } // get_album_ids + + /** + * get_art + * This runs through all of the needs art albums and trys + * to find the art for them from the mp3s + */ + public function get_art($catalog_id = null, $all = false) { + + // Make sure they've actually got methods + $art_order = Config::get('art_order'); + if (!count($art_order)) { + debug_event('gather_art', 'art_order not set, Catalog::get_art aborting', 3); + return true; + } + + // Prevent the script from timing out + set_time_limit(0); + + // If not passed use $this + $catalog_id = $catalog_id ? $catalog_id : $this->id; + + if ($all) { + $albums = $this->get_album_ids(); + } + else { + $albums = array_keys(self::$_art_albums); + } + + // Run through them an get the art! + foreach ($albums as $album_id) { + + // Create the object + $art = new Art($album_id, 'album'); + $album = new Album($album_id); + // We're going to need the name here + $album->format(); + + debug_event('gather_art', 'Gathering art for ' . $album->name, 5); + + // Define the options we want to use for the find art function + $options = array( + 'album_name' => $album->full_name, + 'artist' => $album->artist_name, + 'keyword' => $album->artist_name . ' ' . $album->full_name + ); - // Return results - $results = $art->gather($options, 1); + // Return results + $results = $art->gather($options, 1); - if (count($results)) { - // Pull the string representation from the source - $image = Art::get_from_source($results['0'], 'album'); - if (strlen($image) > '5') { - $art->insert($image, $results['0']['mime']); - // If they've enabled resizing of images generate the thumbnail now - if (Config::get('resize_images')) { - $thumb = $art->generate_thumb($image,array('width'=>275,'height'=>275),$results['0']['mime']); - if (is_array($thumb)) { $art->save_thumb($thumb['thumb'], $thumb['thumb_mime'], '275x275'); } - } - - } - else { - debug_event('gather_art', 'Image less than 5 chars, not inserting', 3); - } - $art_found++; - } + if (count($results)) { + // Pull the string representation from the source + $image = Art::get_from_source($results['0'], 'album'); + if (strlen($image) > '5') { + $art->insert($image, $results['0']['mime']); + // If they've enabled resizing of images generate the thumbnail now + if (Config::get('resize_images')) { + $thumb = $art->generate_thumb($image,array('width'=>275,'height'=>275),$results['0']['mime']); + if (is_array($thumb)) { $art->save_thumb($thumb['thumb'], $thumb['thumb_mime'], '275x275'); } + } + + } + else { + debug_event('gather_art', 'Image less than 5 chars, not inserting', 3); + } + $art_found++; + } - /* Stupid little cutesie thing */ - $search_count++; - if (UI::check_ticker()) { - UI::update_text('count_art_' . $this->id, $search_count); - UI::update_text('read_art_' . $this->id, scrub_out($album->name)); - } //echos song count + /* Stupid little cutesie thing */ + $search_count++; + if (UI::check_ticker()) { + UI::update_text('count_art_' . $this->id, $search_count); + UI::update_text('read_art_' . $this->id, scrub_out($album->name)); + } //echos song count - unset($found); - } // foreach albums + unset($found); + } // foreach albums - // One last time for good measure - UI::update_text('count_art_' . $this->id, $search_count); - UI::update_text('read_art_' . $this->id, scrub_out($album->name)); - - self::$_art_albums = array(); - - } // get_art + // One last time for good measure + UI::update_text('count_art_' . $this->id, $search_count); + UI::update_text('read_art_' . $this->id, scrub_out($album->name)); + + self::$_art_albums = array(); + + } // get_art - /** - * generate_thumbnails - * This generates the thumbnails from the images for object - * of this catalog - */ - public function generate_thumbnails() { - - // Albums first - $albums = $this->get_album_ids(); - - $thumb_count = 0; - - foreach ($albums as $album) { - $art = new Art($album, 'album'); - $image = $art->get(); - - /* Stupid little cutesie thing */ - $thumb_count++; - if (UI::check_ticker()) { - UI::update_text('count_thumb_' . $this->id, $search_count); - } //echos thumb count - - } // end foreach albums - - UI::update_text('count_thumb_' . $this->id, $search_count); - - } // generate_thumbnails - - /** - * get_catalog_albums() - * Returns an array of the albums from a catalog - */ - public static function get_catalog_albums($catalog_id) { - - $results = array(); - - $sql = "SELECT DISTINCT(`song`.`album`) FROM `song` WHERE `song`.`catalog`='$catalog_id'"; - $db_results = Dba::read($sql); - - while ($row = Dba::fetch_assoc($db_results)) { - $results[] = $row['album']; - } - - return $results; - - } // get_catalog_albums - - - /** - * get_catalog_files - * Returns an array of song objects from a catalog, used by sort_files script - */ - public function get_catalog_files($catalog_id=0) { - - $results = array(); - - /* Use $this->id if nothing passed */ - $catalog_id = $catalog_id ? Dba::escape($catalog_id) : Dba::escape($this->id); - - $sql = "SELECT `id` FROM `song` WHERE `catalog`='$catalog_id' AND `enabled`='1'"; - $db_results = Dba::read($sql); - - $results = array(); // return an emty array instead of nothing if no objects - while ($r = Dba::fetch_assoc($db_results)) { - $results[] = new Song($r['id']); - } //end while - - return $results; - - } //get_catalog_files - - /** - * dump_album_art - * This runs through all of the albums and tries to dump the - * art for them into the 'folder.jpg' file in the appropriate dir - */ - public static function dump_album_art($catalog_id, $methods=array()) { + /** + * generate_thumbnails + * This generates the thumbnails from the images for object + * of this catalog + */ + public function generate_thumbnails() { + + // Albums first + $albums = $this->get_album_ids(); + + $thumb_count = 0; + + foreach ($albums as $album) { + $art = new Art($album, 'album'); + $image = $art->get(); + + /* Stupid little cutesie thing */ + $thumb_count++; + if (UI::check_ticker()) { + UI::update_text('count_thumb_' . $this->id, $search_count); + } //echos thumb count + + } // end foreach albums + + UI::update_text('count_thumb_' . $this->id, $search_count); + + } // generate_thumbnails + + /** + * get_catalog_albums() + * Returns an array of the albums from a catalog + */ + public static function get_catalog_albums($catalog_id) { + + $results = array(); + + $sql = "SELECT DISTINCT(`song`.`album`) FROM `song` WHERE `song`.`catalog`='$catalog_id'"; + $db_results = Dba::read($sql); + + while ($row = Dba::fetch_assoc($db_results)) { + $results[] = $row['album']; + } + + return $results; + + } // get_catalog_albums + + + /** + * get_catalog_files + * Returns an array of song objects from a catalog, used by sort_files script + */ + public function get_catalog_files($catalog_id=0) { + + $results = array(); + + /* Use $this->id if nothing passed */ + $catalog_id = $catalog_id ? Dba::escape($catalog_id) : Dba::escape($this->id); + + $sql = "SELECT `id` FROM `song` WHERE `catalog`='$catalog_id' AND `enabled`='1'"; + $db_results = Dba::read($sql); + + $results = array(); // return an emty array instead of nothing if no objects + while ($r = Dba::fetch_assoc($db_results)) { + $results[] = new Song($r['id']); + } //end while + + return $results; + + } //get_catalog_files + + /** + * dump_album_art + * This runs through all of the albums and tries to dump the + * art for them into the 'folder.jpg' file in the appropriate dir + */ + public static function dump_album_art($catalog_id, $methods=array()) { - // Get all of the albums in this catalog - $albums = self::get_catalog_albums($catalog_id); + // Get all of the albums in this catalog + $albums = self::get_catalog_albums($catalog_id); - echo "Starting Dump Album Art...\n"; + echo "Starting Dump Album Art...\n"; - // Run through them and get the art! - foreach ($albums as $album_id) { + // Run through them and get the art! + foreach ($albums as $album_id) { - $album = new Album($album_id); - $art = new Art($album_id, 'album'); - - // If no art, skip - if ( ! $art->get_db() ) { continue; } + $album = new Album($album_id); + $art = new Art($album_id, 'album'); + + // If no art, skip + if ( ! $art->get_db() ) { continue; } - // Get the first song in the album - $songs = $album->get_songs(1); - $song = new Song($songs[0]); - $dir = dirname($song->file); + // Get the first song in the album + $songs = $album->get_songs(1); + $song = new Song($songs[0]); + $dir = dirname($song->file); - $extension = Art::extension($art->raw_mime); - - // Try the preferred filename, if that fails use folder.??? - $preferred_filename = Config::get('album_art_preferred_filename'); - if (!$preferred_filename || - strpos($preferred_filename, '%') !== false) { - $preferred_filename = "folder.$extension"; - } + $extension = Art::extension($art->raw_mime); + + // Try the preferred filename, if that fails use folder.??? + $preferred_filename = Config::get('album_art_preferred_filename'); + if (!$preferred_filename || + strpos($preferred_filename, '%') !== false) { + $preferred_filename = "folder.$extension"; + } - $file = "$dir/$preferred_filename"; - if ($file_handle = fopen($file,"w")) { - if (fwrite($file_handle, $art->raw)) { + $file = "$dir/$preferred_filename"; + if ($file_handle = fopen($file,"w")) { + if (fwrite($file_handle, $art->raw)) { - // Also check and see if we should write - // out some metadata - if ($methods['metadata']) { - switch ($methods['metadata']) { - case 'windows': - $meta_file = $dir . '/desktop.ini'; - $string = "[.ShellClassInfo]\nIconFile=$file\nIconIndex=0\nInfoTip=$album->full_name"; - break; - default: - case 'linux': - $meta_file = $dir . '/.directory'; - $string = "Name=$album->full_name\nIcon=$file"; - break; - } // end switch + // Also check and see if we should write + // out some metadata + if ($methods['metadata']) { + switch ($methods['metadata']) { + case 'windows': + $meta_file = $dir . '/desktop.ini'; + $string = "[.ShellClassInfo]\nIconFile=$file\nIconIndex=0\nInfoTip=$album->full_name"; + break; + default: + case 'linux': + $meta_file = $dir . '/.directory'; + $string = "Name=$album->full_name\nIcon=$file"; + break; + } // end switch - $meta_handle = fopen($meta_file,"w"); - fwrite($meta_handle,$string); - fclose($meta_handle); - - } // end metadata - $i++; - if (!($i%100)) { - echo "Written: $i. . .\n"; - debug_event('art_write',"$album->name Art written to $file",'5'); - } - } // end if fopen - else { - debug_event('art_write',"Unable to open $file for writting",'5'); - echo "Error unable to open file for writting [$file]\n"; - } - } // end if fopen worked - - fclose($file_handle); - - - } // end foreach - - echo "Album Art Dump Complete\n"; - - } // dump_album_art - - /** - * update_last_update - * updates the last_update of the catalog - */ - private function update_last_update() { - - $date = time(); - $sql = "UPDATE `catalog` SET `last_update`='$date' WHERE `id`='$this->id'"; - $db_results = Dba::write($sql); - - } // update_last_update - - /** - * update_last_add - * updates the last_add of the catalog - */ - public function update_last_add() { - - $date = time(); - $sql = "UPDATE `catalog` SET `last_add`='$date' WHERE `id`='$this->id'"; - $db_results = Dba::write($sql); - - } // update_last_add - - /** - * update_last_clean - * This updates the last clean information - */ - public function update_last_clean() { - - $date = time(); - $sql = "UPDATE `catalog` SET `last_clean`='$date' WHERE `id`='$this->id'"; - $db_results = Dba::write($sql); - - } // update_last_clean - - /** - * update_settings - * This function updates the basic setting of the catalog - */ - public static function update_settings($data) { - - $id = Dba::escape($data['catalog_id']); - $name = Dba::escape($data['name']); - $rename = Dba::escape($data['rename_pattern']); - $sort = Dba::escape($data['sort_pattern']); - $remote_username = Dba::escape($data['remote_username']); - $remote_password = Dba::escape($data['remote_password']); - - $sql = "UPDATE `catalog` SET `name`='$name', `rename_pattern`='$rename', " . - "`sort_pattern`='$sort', `remote_username`='$remote_username', `remote_password`='$remote_password' WHERE `id` = '$id'"; - $db_results = Dba::write($sql); - - return true; - - } // update_settings - - /** - * update_single_item - * updates a single album,artist,song from the tag data - * this can be done by 75+ - */ - public static function update_single_item($type,$id) { - - // Because single items are large numbers of things too - set_time_limit(0); - - $songs = array(); - - switch ($type) { - case 'album': - $album = new Album($id); - $songs = $album->get_songs(); - break; - case 'artist': - $artist = new Artist($id); - $songs = $artist->get_songs(); - break; - case 'song': - $songs[] = $id; - break; - } // end switch type - - foreach($songs as $song_id) { - $song = new Song($song_id); - $info = self::update_media_from_tags($song,'',''); - - if ($info['change']) { - $file = scrub_out($song->file); - echo "<dl>\n\t<dd>"; - echo "<strong>$file " . T_('Updated') . "</strong>\n"; - echo $info['text']; - echo "\t</dd>\n</dl><hr align=\"left\" width=\"50%\" />"; - flush(); - } // if change - else { - echo"<dl>\n\t<dd>"; - echo "<strong>" . scrub_out($song->file) . "</strong><br />" . T_('No Update Needed') . "\n"; - echo "\t</dd>\n</dl><hr align=\"left\" width=\"50%\" />"; - flush(); - } - } // foreach songs - - self::gc(); - - } // update_single_item - - /** - * update_media_from_tags - * This is a 'wrapper' function calls the update function for the media - * type in question - */ - public static function update_media_from_tags($media, $sort_pattern='', $rename_pattern='') { - - // Check for patterns - if (!$sort_pattern OR !$rename_pattern) { - $catalog = new Catalog($media->catalog); - $sort_pattern = $catalog->sort_pattern; - $rename_pattern = $catalog->rename_pattern; - } - - debug_event('tag-read','Reading tags from ' . $media->file,'5','ampache-catalog'); - - $vainfo = new vainfo($media->file,'','','',$sort_pattern,$rename_pattern); - $vainfo->get_info(); - - $key = vainfo::get_tag_type($vainfo->tags); - - $results = vainfo::clean_tag_info($vainfo->tags,$key,$media->file); - - // Figure out what type of object this is and call the right - // function, giving it the stuff we've figured out above - $name = (get_class($media) == 'Song') ? 'song' : 'video'; - - $function = 'update_' . $name . '_from_tags'; - - $return = call_user_func(array('Catalog',$function),$results,$media); - - return $return; - - } // update_media_from_tags - - /** - * update_video_from_tags - * Updates the video info based on tags - */ - public static function update_video_from_tags($results,$video) { - - // Pretty sweet function here - return $results; - - } // update_video_from_tags - - /** - * update_song_from_tags - * Updates the song info based on tags; this is called from a bunch of - * different places and passes in a full fledged song object, so it's a - * static function. - * FIXME: This is an ugly mess, this really needs to be consolidated and - * cleaned up. - */ - public static function update_song_from_tags($results,$song) { - - /* Setup the vars */ - $new_song = new Song(); - $new_song->file = $results['file']; - $new_song->title = $results['title']; - $new_song->year = $results['year']; - $new_song->comment = $results['comment']; - $new_song->language = $results['language']; - $new_song->lyrics = $results['lyrics']; - $new_song->bitrate = $results['bitrate']; - $new_song->rate = $results['rate']; - $new_song->mode = ($results['mode'] == 'cbr') ? 'cbr' : 'vbr'; - $new_song->size = $results['size']; - $new_song->time = $results['time']; - $new_song->mime = $results['mime']; - $new_song->track = intval($results['track']); - $new_song->mbid = $results['mb_trackid']; - $artist = $results['artist']; - $artist_mbid = $results['mb_artistid']; - $album = $results['album']; - $album_mbid = $results['mb_albumid']; - $disk = $results['disk']; - $tags = $results['genre']; // multiple genre support makes this an array - - /* - * We have the artist/genre/album name need to check it in the tables - * If found then add & return id, else return id - */ - $new_song->artist = self::check_artist($artist,$artist_mbid); - $new_song->f_artist = $artist; - $new_song->album = self::check_album($album,$new_song->year,$disk,$album_mbid); - $new_song->f_album = $album . " - " . $new_song->year; - $new_song->title = self::check_title($new_song->title,$new_song->file); - - // Nothing to assign here this is a multi-value doodly - // multiple genre support - foreach ($tags as $tag) { - $tag = trim($tag); - self::check_tag($tag,$song->id); - self::check_tag($tag,$new_song->album,'album'); - self::check_tag($tag,$new_song->artist,'artist'); - } - - /* Since we're doing a full compare make sure we fill the extended information */ - $song->fill_ext_info(); - - $info = Song::compare_song_information($song,$new_song); - - if ($info['change']) { - debug_event('update',"$song->file difference found, updating database",'5','ampache-catalog'); - $song->update_song($song->id,$new_song); - // Refine our reference - $song = $new_song; - } - else { - debug_event('update',"$song->file no difference found returning",'5','ampache-catalog'); - } - - return $info; - - } // update_song_from_tags - - /** - * add_to_catalog - * this function adds new files to an - * existing catalog - */ - public function add_to_catalog() { - - if ($this->catalog_type == 'remote') { - UI::show_box_top(T_('Running Remote Update') . '. . .'); - $this->get_remote_catalog($type=0); - UI::show_box_bottom(); - return true; - } - - require Config::get('prefix') . '/templates/show_adds_catalog.inc.php'; - flush(); - - /* Set the Start time */ - $start_time = time(); - - // Make sure the path doesn't end in a / or \ - $this->path = rtrim($this->path,'/'); - $this->path = rtrim($this->path,'\\'); - - // Prevent the script from timing out and flush what we've got - set_time_limit(0); - - /* Get the songs and then insert them into the db */ - $this->add_files($this->path,$type,0,$verbose); - - // Foreach Playlists we found - foreach ($this->_playlists as $full_file) { - $result = $this->import_m3u($full_file); - if ($result['success']) { - $file = basename($full_file); - if ($verbose) { - echo " " . T_('Added Playlist From') . " $file . . . .<br />\n"; - flush(); - } - } // end if import worked - } // end foreach playlist files - - /* Do a little stats mojo here */ - $current_time = time(); - - $catalog_id = $this->id; - require Config::get('prefix') . '/templates/show_gather_art.inc.php'; - flush(); - $this->get_art(); - - /* Update the Catalog last_update */ - $this->update_last_add(); - - $time_diff = ($current_time - $start_time) ?: 0; - $rate = intval($this->count / $time_diff) ?: T_('N/A'); - - UI::show_box_top(); - echo "\n<br />" . - printf(T_('Catalog Update Finished. Total Time: [%s] Total Songs: [%s] Songs Per Second: [%s]'), - date('i:s', $time_diff), $this->count, $rate); - echo '<br /><br />'; - UI::show_box_bottom(); - - } // add_to_catalog - - /** - * get_remote_catalog - * get a remote catalog and runs update if needed this requires - * this uses the AmpacheAPI library provided, replaces legacy XMLRPC - */ - public function get_remote_catalog($type=0) { - - try { - $remote_handle = new AmpacheApi(array('username'=>$this->remote_username,'password'=>$this->remote_password,'server'=>$this->path,'debug'=>true)); - } catch (Exception $e) { - Error::add('general',$e->getMessage()); - Error::display('general'); - flush(); - return false; - } - - if ($remote_handle->state() != 'CONNECTED') { - debug_event('APICLIENT','Error Unable to make API client ready','1'); - Error::add('general', T_('Error Connecting to Remote Server')); - Error::display('general'); - return false; - } - - // Figure out how many songs, more information etc - $remote_catalog_info = $remote_handle->info(); - - // Tell em what we've found johnny! - printf(T_('%u remote catalog(s) found (%u songs)'),$remote_catalog_info['catalogs'],$remote_catalog_info['songs']); - flush(); - - // Hardcoded for now - $step = '500'; - $current = '0'; - $total = $remote_catalog_info['songs']; - - while ($total > $current) { - $start = $current; - $current += $step; - // It uses exceptions so lets try this - try { - $remote_handle->parse_response($remote_handle->send_command('songs',array('offset'=>$start,'limit'=>$step))); - $songs = $remote_handle->get_response(); - } catch (Exception $e) { - Error::add('general',$e->getMessage()); - Error::display('general'); - flush(); - } - // itterate the songs we retrieved and insert them - foreach ($songs as $data) { - if (!$this->insert_remote_song($data['song'])) { - debug_event('REMOTE_INSERT','Remote Insert failed, see previous log messages -' . $data['song']['self']['id'],'1'); - Error::add('general', T_('Unable to Insert Song - %s'),$data['song']['title']); - Error::display('general'); - flush(); - } - } // end foreach - } // end while - - echo "<p>" . T_('Completed updating remote catalog(s)') . ".</p><hr />\n"; - flush(); - - // Update the last update value - $this->update_last_update(); - - return true; - - } // get_remote_catalog - - /** - * update_remote_catalog - * actually updates from the remote data, takes an array of songs that are base64 encoded and parses them - */ - public function update_remote_catalog($data,$root_path) { - - // Going to leave this be for now - //FIXME: Implement - - return true; - - } // update_remote_catalog - - /** - * clean_catalog - * Cleans the catalog of files that no longer exist. - */ - public function clean_catalog() { - - // We don't want to run out of time - set_time_limit(0); - - debug_event('clean', 'Starting on ' . $this->name, 5, 'ampache-catalog'); - - require_once Config::get('prefix') . '/templates/show_clean_catalog.inc.php'; - ob_flush(); - flush(); - - // Do a quick check to make sure that the root of the catalog is - // readable. This will minimize the loss of catalog data if - // mount points fail - if ($this->catalog_type == 'local' && !is_readable($this->path)) { - debug_event('catalog', 'Catalog path:' . $this->path . ' unreadable, clean failed', 1); - Error::add('general', T_('Catalog Root unreadable, stopping clean')); - Error::display('general'); - return false; - } - - - $dead_total = 0; - $stats = self::get_stats($this->id); - foreach(array('video', 'song') as $media_type) { - $total = $stats[$media_type . 's']; // UGLY - if ($total == 0) { - continue; - } - $chunks = floor($total / 10000); - $dead = array(); - foreach(range(0, $chunks) as $chunk) { - $dead = array_merge($dead, $this->_clean_chunk($media_type, $chunk, 10000)); - } - - $dead_count = count($dead); - // The AlmightyOatmeal sanity check - // Never remove everything; it might be a dead mount - if ($dead_count >= $total) { - debug_event('catalog', 'All files would be removed. Doing nothing.', 1); - Error::add('general', T_('All files would be removed. Doing nothing')); - continue; - } - if ($dead_count) { - $dead_total += $dead_count; - $sql = "DELETE FROM `$media_type` WHERE `id` IN " . - '(' . implode(',',$dead) . ')'; - $db_results = Dba::write($sql); - } - debug_event('clean', "$media_type finished, $dead_count removed from " . - $this->name, 5, 'ampache-catalog'); - } - - // Remove any orphaned artists/albums/etc. - self::gc(); - - UI::show_box_top(); - echo "<strong>"; - printf (T_ngettext('Catalog Clean Done. %d file removed.', 'Catalog Clean Done. %d files removed.', $dead_total), $dead_total); - echo "</strong><br />\n"; - UI::show_box_bottom(); - ob_flush(); - flush(); - - $this->update_last_clean(); - } // clean_catalog - - - /** - * _clean_chunk - * This is the clean function, its broken into - * said chunks to try to save a little memory - */ - private function _clean_chunk($media_type, $chunk, $chunk_size) { - debug_event('clean', "Starting chunk $chunk", 5, 'ampache-catalog'); - $dead = array(); - $count = $chunk * $chunk_size; - - $sql = "SELECT `id`, `file` FROM `$media_type` " . - "WHERE `catalog`='$this->id' LIMIT $count,$chunk_size"; - $db_results = Dba::read($sql); - - while ($results = Dba::fetch_assoc($db_results)) { - debug_event('clean', 'Starting work on ' . $results['file'] . '(' . $results['id'] . ')', 5, 'ampache-catalog'); - $count++; - if (UI::check_ticker()) { - $file = str_replace(array('(',')', '\''), '', $results['file']); - UI::update_text('clean_count_' . $this->id, $count); - UI::update_text('clean_dir_' . $this->id, scrub_out($file)); - } - if($this->catalog_type == 'local') { - $file_info = filesize($results['file']); - if (!file_exists($results['file']) || $file_info < 1) { - debug_event('clean', 'File not found or empty: ' . $results['file'], 5, 'ampache-catalog'); - Error::add('general', sprintf(T_('Error File Not Found or 0 Bytes: %s'), $results['file'])); - - - // Store it in an array we'll delete it later... - $dead[] = $results['id']; - - } //if error - else if (!is_readable($results['file'])) { - debug_event('clean', $results['file'] . ' is not readable, but does exist', 1, 'ampache-catalog'); - } - } // if localtype - else { - //do remote url check - $file_info = $this->check_remote_song($results['file']); - - if ($file_info == false) { - /* Add Error */ - Error::add('general', sprintf(T_('Error Remote File Not Found or 0 Bytes: %s'), $results['file'])); - - - // Store it in an array we'll delete it later... - $dead[] = $results['id']; - - } //if error - } // remote catalog - - } //while gettings songs - return $dead; - - } //_clean_chunk - - /** - * verify_catalog - * This function compares the DB's information with the ID3 tags - */ - public function verify_catalog() { - - debug_event('verify', 'Starting on ' . $this->name, 5, 'ampache-catalog'); - set_time_limit(0); - - $stats = self::get_stats($this->id); - $number = $stats['videos'] + $stats['songs']; - $total_updated = 0; - - require_once Config::get('prefix') . '/templates/show_verify_catalog.inc.php'; - - foreach(array('video', 'song') as $media_type) { - $total = $stats[$media_type . 's']; // UGLY - if ($total == 0) { - continue; - } - $chunks = floor($total / 10000); - foreach(range(0, $chunks) as $chunk) { - // Try to be nice about memory usage - if ($chunk > 0) { - $media_type::clear_cache(); - } - $total_updated += $this->_verify_chunk($media_type, $chunk, 10000); - } - } - - debug_event('verify', "Finished, $total_updated updated in " . $this->name, 5, 'ampache-catalog'); - - self::gc(); - $this->update_last_update(); - - UI::show_box_top(); - echo '<strong>'; - printf(T_('Catalog Verify Done. %d of %d files updated.'), $total_updated, $number); - echo "</strong><br />\n"; - UI::show_box_bottom(); - ob_flush(); - flush(); - - return true; - - } // verify_catalog - - /** - * _verify_chunk - * This verifies a chunk of the catalog, done to save - * memory - */ - private function _verify_chunk($media_type, $chunk, $chunk_size) { - debug_event('verify', "Starting chunk $chunk", 5, 'ampache-catalog'); - $count = $chunk * $chunk_size; - $changed = 0; - - $sql = "SELECT `id`, `file` FROM `$media_type` " . - "WHERE `catalog`='$this->id' LIMIT $count,$chunk_size"; - $db_results = Dba::read($sql); - - if (Config::get('memory_cache')) { - while ($row = Dba::fetch_assoc($db_results, false)) { - $media_ids[] = $row['id']; - } - $media_type::build_cache($media_ids); - Dba::seek($db_results, 0); - } - - while ($row = Dba::fetch_assoc($db_results)) { - $count++; - if (UI::check_ticker()) { - $file = str_replace(array('(',')','\''), '', $row['file']); - UI::update_text('verify_count_' . $this->id, $count); - UI::update_text('verify_dir_' . $this->id, scrub_out($file)); - } - - if (!is_readable($row['file'])) { - Error::add('general', sprintf(T_('%s does not exist or is not readable'), $row['file'])); - debug_event('read', $row['file'] . ' does not exist or is not readable', 5,'ampache-catalog'); - continue; - } - - $media = new $media_type($row['id']); - - if (Flag::has_flag($media->id, $type)) { - debug_event('verify', "$media->file is flagged, skipping", 5, 'ampache-catalog'); - continue; - } - - $info = self::update_media_from_tags($media, $this->sort_pattern,$this->rename_pattern); - if ($info['change']) { - $changed++; - } - unset($info); - } - - UI::update_text('verify_count_' . $this->id, $count); - return $changed; - - } // _verfiy_chunk - - /** - * gc - * - * This is a wrapper function for all of the different cleaning - * functions, it runs them in an order that resembles correct - */ - public static function gc() { - - debug_event('catalog', 'Database cleanup started', 5, 'ampache-catalog'); - Song::gc(); - Album::gc(); - Artist::gc(); - Art::gc(); - Flag::gc(); - Stats::gc(); - Rating::gc(); - Playlist::gc(); - Tmp_Playlist::gc(); - Shoutbox::gc(); - Tag::gc(); - debug_event('catalog', 'Database cleanup ended', 5, 'ampache-catalog'); - - } - - /** - * trim_prefix - * Splits the prefix from the string - */ - public static function trim_prefix($string) { - $prefix_pattern = '/^(' . implode('\\s|',explode('|',Config::get('catalog_prefix_pattern'))) . '\\s)(.*)/i'; - preg_match($prefix_pattern, $string, $matches); - - if (count($matches)) { - $string = trim($matches[2]); - $prefix = trim($matches[1]); - } - else { - $prefix = null; - } - - return array('string' => $string, 'prefix' => $prefix); - } // trim_prefix - - /** - * check_artist - * $artist checks if there then return id else insert and return id - * If readonly is passed then don't create, return false on not found - */ - public static function check_artist($artist,$mbid='',$readonly='') { - - /* Clean up the artist */ - $artist = trim($artist); - $artist = Dba::escape($artist); - - /* Ohh no the artist has lost it's mojo! */ - if (!$artist) { - $artist = T_('Unknown (Orphaned)'); - } - - // Remove the prefix so we can sort it correctly - $trimmed = Catalog::trim_prefix($artist); - $artist = $trimmed['string']; - $prefix = $trimmed['prefix']; - - // Check to see if we've seen this artist before - if (isset(self::$artists[$artist][$mbid])) { - return self::$artists[$artist][$mbid]; - } // if we've seen this artist before - - $exists = false; - - $sql = "SELECT `id` FROM `artist` WHERE `mbid`='$mbid'"; - $db_results = Dba::read($sql); - - // Check for results - if ($r = Dba::fetch_assoc($db_results)) { - $artist_id = $r['id']; - $exists = true; - } - - else { // No exact match based on MBID - $sql = "SELECT `id`, `mbid` FROM `artist` WHERE `name` LIKE '$artist'"; - $db_results = Dba::read($sql); - - - /* If we have results */ - while ($r = Dba::fetch_assoc($db_results)) { - $key = is_null($r['mbid']) ? 'null' : $r['mbid']; - $id_array[$key] = $r['id']; - } // while - - /* Choose one */ - if (isset($id_array)) { - if ($mbid == '') { // Prefer null entry, otherwise pick the first - if (isset($id_array['null'])) { - $key = 'null'; - } - else { - $keys = array_keys($id_array); - $key = array_shift($keys); - } - $artist_id = $id_array[$key]; - $exists = true; - } - elseif (isset($id_array['null'])) { - $artist_id = $id_array['null']; - $exists = true; - if (!$readonly) { - $sql = "UPDATE `artist` SET `mbid`='$mbid' WHERE `id`='$artist_id'"; - $db_results = Dba::write($sql); - if (!$db_results) { - Error::add('general', sprintf(T_('Updating Artist: %s'), $artist)); - } - } - } - unset($id_array); - } - } // fuzzy matching - - /* If not found create */ - if (!$readonly && !$exists) { - - $prefix_txt = $prefix ? "'$prefix'" : 'NULL'; - - $mbid = $mbid == '' ? 'NULL' : "'$mbid'"; - - $sql = "INSERT INTO `artist` (`name`, `prefix`, `mbid`) " . - "VALUES ('$artist',$prefix_txt,$mbid)"; - $db_results = Dba::write($sql); - $artist_id = Dba::insert_id(); - - if (!$db_results) { - Error::add('general', sprintf(T_('Inserting Artist: %s'), $artist)); - } - - } // not found - // If readonly, and not found return false - elseif (!$exists) { - return false; - } - - self::$artists[$artist][$mbid] = $artist_id; - - return $artist_id; - - } // check_artist - - /** - * check_album - * Searches for album; if found returns id else inserts and returns id - */ - public static function check_album($album, $album_year = 0, - $album_disk = 0, $mbid = '', $readonly = false) { - - /* Clean up the values */ - $album = trim($album); - $album = Dba::escape($album); - // Not even sure if these can be negative, but better safe than - // llama. - $album_year = abs(intval($album_year)); - $album_disk = abs(intval($album_disk)); - - /* Ohh no the album has lost its mojo */ - if (!$album) { - $album = T_('Unknown (Orphaned)'); - unset($album_year, $album_disk); - } - - // Remove the prefix so we can sort it correctly - $trimmed = Catalog::trim_prefix($album); - $album = $trimmed['string']; - $prefix = $trimmed['prefix']; - - // Check to see if we've seen this album before - if (isset(self::$albums[$album][$album_year][$album_disk][$mbid])) { - return self::$albums[$album][$album_year][$album_disk][$mbid]; - } - - /* Set up the Query */ - $sql = "SELECT `id` FROM `album` WHERE `name` = '$album'" . - " AND `disk`='$album_disk' AND `year`='$album_year'" . - " AND `mbid`" . ($mbid ? "='$mbid'" : ' IS NULL') . - " AND `prefix`" . ($prefix ? "='$prefix'" : ' IS NULL'); - - $db_results = Dba::read($sql); - - /* If it's found */ - if ($r = Dba::fetch_assoc($db_results)) { - $album_id = $r['id']; - - // If we don't have art put it in the 'needs me some - // art' array - $art = new Art($r['id'], 'album'); - $art->get_db(); - if (!$art->raw) { - $key = $r['id']; - self::$_art_albums[$key] = $key; - } - - } //if found - elseif (!$readonly) { // If not found, create - - $prefix = $prefix ? "'$prefix'" : 'NULL'; - $mbid = $mbid ? "'$mbid'" : 'NULL'; - - $sql = "INSERT INTO `album` (`name`, `prefix`,`year`,`disk`,`mbid`) " . - "VALUES ('$album',$prefix,'$album_year','$album_disk',$mbid)"; - $db_results = Dba::write($sql); - $album_id = Dba::insert_id(); - - if (!$db_results) { - debug_event('album',"Error Unable to insert Album:$album",'2'); - return false; - } - - // Add it to the I needs me some album art array - self::$_art_albums[$album_id] = $album_id; - - } //not found - else { - // readonly and not found - return false; - } - - // Save the cache - self::$albums[$album][$album_year][$album_disk][$mbid] = $album_id; - - return $album_id; - - } // check_album - - /** - * check_tag - * This checks the tag we've been passed (name) - * and sees if it exists, and if so if it's mapped - * to this object, this is only done for songs for now - */ - public static function check_tag($value,$object_id,$object_type='song') { - - $map_id = Tag::add($object_type,$object_id,$value,'0'); - - return $map_id; - - } // check_tag - - /** - * check_title - * this checks to make sure something is - * set on the title, if it isn't it looks at the - * filename and trys to set the title based on that - */ - public static function check_title($title,$file=0) { - - if (strlen(trim($title)) < 1) { - $title = Dba::escape($file); - } - - return $title; - - } // check_title - - /** - * insert_local_song - * Insert a song that isn't already in the database this - * function is in here so we don't have to create a song object - */ - public function insert_local_song($file,$file_info) { - - /* Create the vainfo object and get info */ - $vainfo = new vainfo($file,'','','',$this->sort_pattern,$this->rename_pattern); - $vainfo->get_info(); - - $key = vainfo::get_tag_type($vainfo->tags); - - /* Clean Up the tags */ - $results = vainfo::clean_tag_info($vainfo->tags,$key,$file); - - /* Set the vars here... so we don't have to do the '" . $blah['asd'] . "' */ - $title = Dba::escape($results['title']); - $artist = $results['artist']; - $album = $results['album']; - $bitrate = $results['bitrate']; - $rate = $results['rate']; - $mode = $results['mode']; - $size = $results['size']; - $song_time = $results['time']; - $track = $results['track']; - $track_mbid = $results['mb_trackid']; - $album_mbid = $results['mb_albumid']; - $artist_mbid= $results['mb_artistid']; - $disk = $results['disk']; - $year = $results['year']; - $comment = $results['comment']; - $tags = $results['genre']; // multiple genre support makes this an array - $current_time = time(); - $lyrics = ' '; - - /* - * We have the artist/genre/album name need to check it in the tables - * If found then add & return id, else return id - */ - $artist_id = self::check_artist($artist,$artist_mbid); - $album_id = self::check_album($album,$year,$disk,$album_mbid); - $title = self::check_title($title,$file); - $add_file = Dba::escape($file); - - $sql = "INSERT INTO `song` (file,catalog,album,artist,title,bitrate,rate,mode,size,time,track,addition_time,year,mbid)" . - " VALUES ('$add_file','$this->id','$album_id','$artist_id','$title','$bitrate','$rate','$mode','$size','$song_time','$track','$current_time','$year','$track_mbid')"; - $db_results = Dba::write($sql); - - if (!$db_results) { - debug_event('insert',"Unable to insert $file -- $sql" . Dba::error(),'5','ampache-catalog'); - Error::add('catalog_add', sprintf(T_('SQL Error Adding %s'), $file)); - } - - $song_id = Dba::insert_id(); - - // multiple genre support - foreach ($tags as $tag) { - $tag = trim($tag); - self::check_tag($tag,$song_id); - self::check_tag($tag,$album_id,'album'); - self::check_tag($tag,$artist_id,'artist'); - } - - - /* Add the EXT information */ - $sql = "INSERT INTO `song_data` (`song_id`,`comment`,`lyrics`) " . - " VALUES ('$song_id','$comment','$lyrics')"; - $db_results = Dba::write($sql); - - if (!$db_results) { - debug_event('insert',"Unable to insert EXT Info for $file -- $sql",'5','ampache-catalog'); - } - - } // insert_local_song - - /** - * insert_remote_song - * takes the information gotten from XML-RPC and - * inserts it into the local database. The filename - * ends up being the url. - */ - public function insert_remote_song($song) { - - /* Limitations: - * Missing Following Metadata - * Disk,Rate - */ - - // Strip the SSID off of the url, we will need to regenerate this every time - $url = preg_replace("/ssid=.*&/","",$song['url']); - $title = Dba::escape($song['title']); - $album = self::check_album($song['album'],$song['year'],null,$song['album_mbid']); - $artist = self::check_artist($song['artist'],$song['artist_mbid']); - $bitrate = Dba::escape($song['bitrate']); - $size = Dba::escape($song['size']); - $song_time = Dba::escape($song['time']); - $track = Dba::escape($song['track']); - $year = Dba::escape($song['year']); - $title = Dba::escape($song['title']); - $mbid = Dba::escape($song['mbid']); - $mode = Dba::escape($song['mode']); - $current_time = time(); - $catalog_id = Dba::escape($this->id); - - $sql = "INSERT INTO `song` (`file`,`catalog`,`album`,`artist`,`title`,`bitrate`,`rate`,`mode`,`size`,`time`,`track`,`addition_time`,`year`,`mbid`)" . - " VALUES ('$url','$catalog_id','$album','$artist','$title','$bitrate','$rate','$mode','$size','$song_time','$track','$current_time','$year','$mbid')"; - $db_results = Dba::write($sql); - - if (!$db_results) { - debug_event('insert',"Unable to Add Remote $url -- $sql",'5','ampache-catalog'); - return false; - } - - // Weird to do this here, but we have the information - see if the album has art, if it doesn't then use the remote - // art url - $art = new Art($album, 'album'); - // If it doesn't have art... - if (!$art->get()) { - // Get the mime out - $get_vars = parse_url($song['art']); - $extension = substr($get_vars['query'],strlen($get_vars['query'])-3,3); - // Pull the image - $raw = Art::get_from_source( - array('url' => $song['art']), 'album'); - $inserted = $art->insert($raw,'image/' . $extension); - } - - return true; - - } // insert_remote_song - - /** - * insert_local_video - * This inserts a video file into the video file table the tag - * information we can get is super sketchy so it's kind of a crap shoot - * here - */ - public function insert_local_video($file,$filesize) { - - /* Create the vainfo object and get info */ - $vainfo = new vainfo($file,'','','',$this->sort_pattern,$this->rename_pattern); - $vainfo->get_info(); - - $tag_name = vainfo::get_tag_type($vainfo->tags); - $results = vainfo::clean_tag_info($vainfo->tags,$tag_name,$file); - - - $file = Dba::escape($file); - $catalog_id = Dba::escape($this->id); - $title = Dba::escape($results['title']); - $vcodec = $results['video_codec']; - $acodec = $results['audio_codec']; - $rezx = intval($results['resolution_x']); - $rezy = intval($results['resolution_y']); - $filesize = Dba::escape($filesize); - $time = Dba::escape($results['time']); - $mime = Dba::escape($results['mime']); - // UNUSED CURRENTLY - $comment = Dba::escape($results['comment']); - $year = Dba::escape($results['year']); - $disk = Dba::escape($results['disk']); - - $sql = "INSERT INTO `video` (`file`,`catalog`,`title`,`video_codec`,`audio_codec`,`resolution_x`,`resolution_y`,`size`,`time`,`mime`) " . - " VALUES ('$file','$catalog_id','$title','$vcodec','$acodec','$rezx','$rezy','$filesize','$time','$mime')"; - $db_results = Dba::write($sql); - - return true; - - } // insert_local_video - - /** - * check_remote_song - * checks to see if a remote song exists in the database or not - * if it find a song it returns the UID - */ - public function check_remote_song($url) { - - $url = Dba::escape($url); - - $sql = "SELECT `id` FROM `song` WHERE `file`='$url'"; - $db_results = Dba::read($sql); - - if ($results = Dba::fetch_assoc($db_results)) { - return $results['id']; - } - - return false; - - } // check_remote_song - - /** - * check_local_mp3 - * Checks the song to see if it's there already returns true if found, false if not - */ - public function check_local_mp3($full_file, $gather_type='') { - - $file_date = filemtime($full_file); - if ($file_date < $this->last_add) { - debug_event('Check','Skipping ' . $full_file . ' File modify time before last add run','3'); - return true; - } - - $full_file = Dba::escape($full_file); + $meta_handle = fopen($meta_file,"w"); + fwrite($meta_handle,$string); + fclose($meta_handle); + + } // end metadata + $i++; + if (!($i%100)) { + echo "Written: $i. . .\n"; + debug_event('art_write',"$album->name Art written to $file",'5'); + } + } // end if fopen + else { + debug_event('art_write',"Unable to open $file for writting",'5'); + echo "Error unable to open file for writting [$file]\n"; + } + } // end if fopen worked + + fclose($file_handle); + + + } // end foreach + + echo "Album Art Dump Complete\n"; + + } // dump_album_art + + /** + * update_last_update + * updates the last_update of the catalog + */ + private function update_last_update() { + + $date = time(); + $sql = "UPDATE `catalog` SET `last_update`='$date' WHERE `id`='$this->id'"; + $db_results = Dba::write($sql); + + } // update_last_update + + /** + * update_last_add + * updates the last_add of the catalog + */ + public function update_last_add() { + + $date = time(); + $sql = "UPDATE `catalog` SET `last_add`='$date' WHERE `id`='$this->id'"; + $db_results = Dba::write($sql); + + } // update_last_add + + /** + * update_last_clean + * This updates the last clean information + */ + public function update_last_clean() { + + $date = time(); + $sql = "UPDATE `catalog` SET `last_clean`='$date' WHERE `id`='$this->id'"; + $db_results = Dba::write($sql); + + } // update_last_clean + + /** + * update_settings + * This function updates the basic setting of the catalog + */ + public static function update_settings($data) { + + $id = Dba::escape($data['catalog_id']); + $name = Dba::escape($data['name']); + $rename = Dba::escape($data['rename_pattern']); + $sort = Dba::escape($data['sort_pattern']); + $remote_username = Dba::escape($data['remote_username']); + $remote_password = Dba::escape($data['remote_password']); + + $sql = "UPDATE `catalog` SET `name`='$name', `rename_pattern`='$rename', " . + "`sort_pattern`='$sort', `remote_username`='$remote_username', `remote_password`='$remote_password' WHERE `id` = '$id'"; + $db_results = Dba::write($sql); + + return true; + + } // update_settings + + /** + * update_single_item + * updates a single album,artist,song from the tag data + * this can be done by 75+ + */ + public static function update_single_item($type,$id) { + + // Because single items are large numbers of things too + set_time_limit(0); + + $songs = array(); + + switch ($type) { + case 'album': + $album = new Album($id); + $songs = $album->get_songs(); + break; + case 'artist': + $artist = new Artist($id); + $songs = $artist->get_songs(); + break; + case 'song': + $songs[] = $id; + break; + } // end switch type + + foreach($songs as $song_id) { + $song = new Song($song_id); + $info = self::update_media_from_tags($song,'',''); + + if ($info['change']) { + $file = scrub_out($song->file); + echo "<dl>\n\t<dd>"; + echo "<strong>$file " . T_('Updated') . "</strong>\n"; + echo $info['text']; + echo "\t</dd>\n</dl><hr align=\"left\" width=\"50%\" />"; + flush(); + } // if change + else { + echo"<dl>\n\t<dd>"; + echo "<strong>" . scrub_out($song->file) . "</strong><br />" . T_('No Update Needed') . "\n"; + echo "\t</dd>\n</dl><hr align=\"left\" width=\"50%\" />"; + flush(); + } + } // foreach songs + + self::gc(); + + } // update_single_item + + /** + * update_media_from_tags + * This is a 'wrapper' function calls the update function for the media + * type in question + */ + public static function update_media_from_tags($media, $sort_pattern='', $rename_pattern='') { + + // Check for patterns + if (!$sort_pattern OR !$rename_pattern) { + $catalog = new Catalog($media->catalog); + $sort_pattern = $catalog->sort_pattern; + $rename_pattern = $catalog->rename_pattern; + } + + debug_event('tag-read','Reading tags from ' . $media->file,'5','ampache-catalog'); + + $vainfo = new vainfo($media->file,'','','',$sort_pattern,$rename_pattern); + $vainfo->get_info(); + + $key = vainfo::get_tag_type($vainfo->tags); + + $results = vainfo::clean_tag_info($vainfo->tags,$key,$media->file); + + // Figure out what type of object this is and call the right + // function, giving it the stuff we've figured out above + $name = (get_class($media) == 'Song') ? 'song' : 'video'; + + $function = 'update_' . $name . '_from_tags'; + + $return = call_user_func(array('Catalog',$function),$results,$media); + + return $return; + + } // update_media_from_tags + + /** + * update_video_from_tags + * Updates the video info based on tags + */ + public static function update_video_from_tags($results,$video) { + + // Pretty sweet function here + return $results; + + } // update_video_from_tags + + /** + * update_song_from_tags + * Updates the song info based on tags; this is called from a bunch of + * different places and passes in a full fledged song object, so it's a + * static function. + * FIXME: This is an ugly mess, this really needs to be consolidated and + * cleaned up. + */ + public static function update_song_from_tags($results,$song) { + + /* Setup the vars */ + $new_song = new Song(); + $new_song->file = $results['file']; + $new_song->title = $results['title']; + $new_song->year = $results['year']; + $new_song->comment = $results['comment']; + $new_song->language = $results['language']; + $new_song->lyrics = $results['lyrics']; + $new_song->bitrate = $results['bitrate']; + $new_song->rate = $results['rate']; + $new_song->mode = ($results['mode'] == 'cbr') ? 'cbr' : 'vbr'; + $new_song->size = $results['size']; + $new_song->time = $results['time']; + $new_song->mime = $results['mime']; + $new_song->track = intval($results['track']); + $new_song->mbid = $results['mb_trackid']; + $artist = $results['artist']; + $artist_mbid = $results['mb_artistid']; + $album = $results['album']; + $album_mbid = $results['mb_albumid']; + $disk = $results['disk']; + $tags = $results['genre']; // multiple genre support makes this an array + + /* + * We have the artist/genre/album name need to check it in the tables + * If found then add & return id, else return id + */ + $new_song->artist = self::check_artist($artist,$artist_mbid); + $new_song->f_artist = $artist; + $new_song->album = self::check_album($album,$new_song->year,$disk,$album_mbid); + $new_song->f_album = $album . " - " . $new_song->year; + $new_song->title = self::check_title($new_song->title,$new_song->file); + + // Nothing to assign here this is a multi-value doodly + // multiple genre support + foreach ($tags as $tag) { + $tag = trim($tag); + self::check_tag($tag,$song->id); + self::check_tag($tag,$new_song->album,'album'); + self::check_tag($tag,$new_song->artist,'artist'); + } + + /* Since we're doing a full compare make sure we fill the extended information */ + $song->fill_ext_info(); + + $info = Song::compare_song_information($song,$new_song); + + if ($info['change']) { + debug_event('update',"$song->file difference found, updating database",'5','ampache-catalog'); + $song->update_song($song->id,$new_song); + // Refine our reference + $song = $new_song; + } + else { + debug_event('update',"$song->file no difference found returning",'5','ampache-catalog'); + } + + return $info; + + } // update_song_from_tags + + /** + * add_to_catalog + * this function adds new files to an + * existing catalog + */ + public function add_to_catalog() { + + if ($this->catalog_type == 'remote') { + UI::show_box_top(T_('Running Remote Update') . '. . .'); + $this->get_remote_catalog($type=0); + UI::show_box_bottom(); + return true; + } + + require Config::get('prefix') . '/templates/show_adds_catalog.inc.php'; + flush(); + + /* Set the Start time */ + $start_time = time(); + + // Make sure the path doesn't end in a / or \ + $this->path = rtrim($this->path,'/'); + $this->path = rtrim($this->path,'\\'); + + // Prevent the script from timing out and flush what we've got + set_time_limit(0); + + /* Get the songs and then insert them into the db */ + $this->add_files($this->path,$type,0,$verbose); + + // Foreach Playlists we found + foreach ($this->_playlists as $full_file) { + $result = $this->import_m3u($full_file); + if ($result['success']) { + $file = basename($full_file); + if ($verbose) { + echo " " . T_('Added Playlist From') . " $file . . . .<br />\n"; + flush(); + } + } // end if import worked + } // end foreach playlist files + + /* Do a little stats mojo here */ + $current_time = time(); + + $catalog_id = $this->id; + require Config::get('prefix') . '/templates/show_gather_art.inc.php'; + flush(); + $this->get_art(); + + /* Update the Catalog last_update */ + $this->update_last_add(); + + $time_diff = ($current_time - $start_time) ?: 0; + $rate = intval($this->count / $time_diff) ?: T_('N/A'); + + UI::show_box_top(); + echo "\n<br />" . + printf(T_('Catalog Update Finished. Total Time: [%s] Total Songs: [%s] Songs Per Second: [%s]'), + date('i:s', $time_diff), $this->count, $rate); + echo '<br /><br />'; + UI::show_box_bottom(); + + } // add_to_catalog + + /** + * get_remote_catalog + * get a remote catalog and runs update if needed this requires + * this uses the AmpacheAPI library provided, replaces legacy XMLRPC + */ + public function get_remote_catalog($type=0) { + + try { + $remote_handle = new AmpacheApi(array('username'=>$this->remote_username,'password'=>$this->remote_password,'server'=>$this->path,'debug'=>true)); + } catch (Exception $e) { + Error::add('general',$e->getMessage()); + Error::display('general'); + flush(); + return false; + } + + if ($remote_handle->state() != 'CONNECTED') { + debug_event('APICLIENT','Error Unable to make API client ready','1'); + Error::add('general', T_('Error Connecting to Remote Server')); + Error::display('general'); + return false; + } + + // Figure out how many songs, more information etc + $remote_catalog_info = $remote_handle->info(); + + // Tell em what we've found johnny! + printf(T_('%u remote catalog(s) found (%u songs)'),$remote_catalog_info['catalogs'],$remote_catalog_info['songs']); + flush(); + + // Hardcoded for now + $step = '500'; + $current = '0'; + $total = $remote_catalog_info['songs']; + + while ($total > $current) { + $start = $current; + $current += $step; + // It uses exceptions so lets try this + try { + $remote_handle->parse_response($remote_handle->send_command('songs',array('offset'=>$start,'limit'=>$step))); + $songs = $remote_handle->get_response(); + } catch (Exception $e) { + Error::add('general',$e->getMessage()); + Error::display('general'); + flush(); + } + // itterate the songs we retrieved and insert them + foreach ($songs as $data) { + if (!$this->insert_remote_song($data['song'])) { + debug_event('REMOTE_INSERT','Remote Insert failed, see previous log messages -' . $data['song']['self']['id'],'1'); + Error::add('general', T_('Unable to Insert Song - %s'),$data['song']['title']); + Error::display('general'); + flush(); + } + } // end foreach + } // end while + + echo "<p>" . T_('Completed updating remote catalog(s)') . ".</p><hr />\n"; + flush(); + + // Update the last update value + $this->update_last_update(); + + return true; + + } // get_remote_catalog + + /** + * update_remote_catalog + * actually updates from the remote data, takes an array of songs that are base64 encoded and parses them + */ + public function update_remote_catalog($data,$root_path) { + + // Going to leave this be for now + //FIXME: Implement + + return true; + + } // update_remote_catalog + + /** + * clean_catalog + * Cleans the catalog of files that no longer exist. + */ + public function clean_catalog() { + + // We don't want to run out of time + set_time_limit(0); + + debug_event('clean', 'Starting on ' . $this->name, 5, 'ampache-catalog'); + + require_once Config::get('prefix') . '/templates/show_clean_catalog.inc.php'; + ob_flush(); + flush(); + + // Do a quick check to make sure that the root of the catalog is + // readable. This will minimize the loss of catalog data if + // mount points fail + if ($this->catalog_type == 'local' && !is_readable($this->path)) { + debug_event('catalog', 'Catalog path:' . $this->path . ' unreadable, clean failed', 1); + Error::add('general', T_('Catalog Root unreadable, stopping clean')); + Error::display('general'); + return false; + } + + + $dead_total = 0; + $stats = self::get_stats($this->id); + foreach(array('video', 'song') as $media_type) { + $total = $stats[$media_type . 's']; // UGLY + if ($total == 0) { + continue; + } + $chunks = floor($total / 10000); + $dead = array(); + foreach(range(0, $chunks) as $chunk) { + $dead = array_merge($dead, $this->_clean_chunk($media_type, $chunk, 10000)); + } + + $dead_count = count($dead); + // The AlmightyOatmeal sanity check + // Never remove everything; it might be a dead mount + if ($dead_count >= $total) { + debug_event('catalog', 'All files would be removed. Doing nothing.', 1); + Error::add('general', T_('All files would be removed. Doing nothing')); + continue; + } + if ($dead_count) { + $dead_total += $dead_count; + $sql = "DELETE FROM `$media_type` WHERE `id` IN " . + '(' . implode(',',$dead) . ')'; + $db_results = Dba::write($sql); + } + debug_event('clean', "$media_type finished, $dead_count removed from " . + $this->name, 5, 'ampache-catalog'); + } + + // Remove any orphaned artists/albums/etc. + self::gc(); + + UI::show_box_top(); + echo "<strong>"; + printf (T_ngettext('Catalog Clean Done. %d file removed.', 'Catalog Clean Done. %d files removed.', $dead_total), $dead_total); + echo "</strong><br />\n"; + UI::show_box_bottom(); + ob_flush(); + flush(); + + $this->update_last_clean(); + } // clean_catalog + + + /** + * _clean_chunk + * This is the clean function, its broken into + * said chunks to try to save a little memory + */ + private function _clean_chunk($media_type, $chunk, $chunk_size) { + debug_event('clean', "Starting chunk $chunk", 5, 'ampache-catalog'); + $dead = array(); + $count = $chunk * $chunk_size; + + $sql = "SELECT `id`, `file` FROM `$media_type` " . + "WHERE `catalog`='$this->id' LIMIT $count,$chunk_size"; + $db_results = Dba::read($sql); + + while ($results = Dba::fetch_assoc($db_results)) { + debug_event('clean', 'Starting work on ' . $results['file'] . '(' . $results['id'] . ')', 5, 'ampache-catalog'); + $count++; + if (UI::check_ticker()) { + $file = str_replace(array('(',')', '\''), '', $results['file']); + UI::update_text('clean_count_' . $this->id, $count); + UI::update_text('clean_dir_' . $this->id, scrub_out($file)); + } + if($this->catalog_type == 'local') { + $file_info = filesize($results['file']); + if (!file_exists($results['file']) || $file_info < 1) { + debug_event('clean', 'File not found or empty: ' . $results['file'], 5, 'ampache-catalog'); + Error::add('general', sprintf(T_('Error File Not Found or 0 Bytes: %s'), $results['file'])); + + + // Store it in an array we'll delete it later... + $dead[] = $results['id']; + + } //if error + else if (!is_readable($results['file'])) { + debug_event('clean', $results['file'] . ' is not readable, but does exist', 1, 'ampache-catalog'); + } + } // if localtype + else { + //do remote url check + $file_info = $this->check_remote_song($results['file']); + + if ($file_info == false) { + /* Add Error */ + Error::add('general', sprintf(T_('Error Remote File Not Found or 0 Bytes: %s'), $results['file'])); + + + // Store it in an array we'll delete it later... + $dead[] = $results['id']; + + } //if error + } // remote catalog + + } //while gettings songs + return $dead; + + } //_clean_chunk + + /** + * verify_catalog + * This function compares the DB's information with the ID3 tags + */ + public function verify_catalog() { + + debug_event('verify', 'Starting on ' . $this->name, 5, 'ampache-catalog'); + set_time_limit(0); + + $stats = self::get_stats($this->id); + $number = $stats['videos'] + $stats['songs']; + $total_updated = 0; + + require_once Config::get('prefix') . '/templates/show_verify_catalog.inc.php'; + + foreach(array('video', 'song') as $media_type) { + $total = $stats[$media_type . 's']; // UGLY + if ($total == 0) { + continue; + } + $chunks = floor($total / 10000); + foreach(range(0, $chunks) as $chunk) { + // Try to be nice about memory usage + if ($chunk > 0) { + $media_type::clear_cache(); + } + $total_updated += $this->_verify_chunk($media_type, $chunk, 10000); + } + } + + debug_event('verify', "Finished, $total_updated updated in " . $this->name, 5, 'ampache-catalog'); + + self::gc(); + $this->update_last_update(); + + UI::show_box_top(); + echo '<strong>'; + printf(T_('Catalog Verify Done. %d of %d files updated.'), $total_updated, $number); + echo "</strong><br />\n"; + UI::show_box_bottom(); + ob_flush(); + flush(); + + return true; + + } // verify_catalog + + /** + * _verify_chunk + * This verifies a chunk of the catalog, done to save + * memory + */ + private function _verify_chunk($media_type, $chunk, $chunk_size) { + debug_event('verify', "Starting chunk $chunk", 5, 'ampache-catalog'); + $count = $chunk * $chunk_size; + $changed = 0; + + $sql = "SELECT `id`, `file` FROM `$media_type` " . + "WHERE `catalog`='$this->id' LIMIT $count,$chunk_size"; + $db_results = Dba::read($sql); + + if (Config::get('memory_cache')) { + while ($row = Dba::fetch_assoc($db_results, false)) { + $media_ids[] = $row['id']; + } + $media_type::build_cache($media_ids); + Dba::seek($db_results, 0); + } + + while ($row = Dba::fetch_assoc($db_results)) { + $count++; + if (UI::check_ticker()) { + $file = str_replace(array('(',')','\''), '', $row['file']); + UI::update_text('verify_count_' . $this->id, $count); + UI::update_text('verify_dir_' . $this->id, scrub_out($file)); + } + + if (!is_readable($row['file'])) { + Error::add('general', sprintf(T_('%s does not exist or is not readable'), $row['file'])); + debug_event('read', $row['file'] . ' does not exist or is not readable', 5,'ampache-catalog'); + continue; + } + + $media = new $media_type($row['id']); + + if (Flag::has_flag($media->id, $type)) { + debug_event('verify', "$media->file is flagged, skipping", 5, 'ampache-catalog'); + continue; + } + + $info = self::update_media_from_tags($media, $this->sort_pattern,$this->rename_pattern); + if ($info['change']) { + $changed++; + } + unset($info); + } + + UI::update_text('verify_count_' . $this->id, $count); + return $changed; + + } // _verfiy_chunk + + /** + * gc + * + * This is a wrapper function for all of the different cleaning + * functions, it runs them in an order that resembles correct + */ + public static function gc() { + + debug_event('catalog', 'Database cleanup started', 5, 'ampache-catalog'); + Song::gc(); + Album::gc(); + Artist::gc(); + Art::gc(); + Flag::gc(); + Stats::gc(); + Rating::gc(); + Playlist::gc(); + Tmp_Playlist::gc(); + Shoutbox::gc(); + Tag::gc(); + debug_event('catalog', 'Database cleanup ended', 5, 'ampache-catalog'); + + } + + /** + * trim_prefix + * Splits the prefix from the string + */ + public static function trim_prefix($string) { + $prefix_pattern = '/^(' . implode('\\s|',explode('|',Config::get('catalog_prefix_pattern'))) . '\\s)(.*)/i'; + preg_match($prefix_pattern, $string, $matches); + + if (count($matches)) { + $string = trim($matches[2]); + $prefix = trim($matches[1]); + } + else { + $prefix = null; + } + + return array('string' => $string, 'prefix' => $prefix); + } // trim_prefix + + /** + * check_artist + * $artist checks if there then return id else insert and return id + * If readonly is passed then don't create, return false on not found + */ + public static function check_artist($artist,$mbid='',$readonly='') { + + /* Clean up the artist */ + $artist = trim($artist); + $artist = Dba::escape($artist); + + /* Ohh no the artist has lost it's mojo! */ + if (!$artist) { + $artist = T_('Unknown (Orphaned)'); + } + + // Remove the prefix so we can sort it correctly + $trimmed = Catalog::trim_prefix($artist); + $artist = $trimmed['string']; + $prefix = $trimmed['prefix']; + + // Check to see if we've seen this artist before + if (isset(self::$artists[$artist][$mbid])) { + return self::$artists[$artist][$mbid]; + } // if we've seen this artist before + + $exists = false; + + $sql = "SELECT `id` FROM `artist` WHERE `mbid`='$mbid'"; + $db_results = Dba::read($sql); + + // Check for results + if ($r = Dba::fetch_assoc($db_results)) { + $artist_id = $r['id']; + $exists = true; + } + + else { // No exact match based on MBID + $sql = "SELECT `id`, `mbid` FROM `artist` WHERE `name` LIKE '$artist'"; + $db_results = Dba::read($sql); + + + /* If we have results */ + while ($r = Dba::fetch_assoc($db_results)) { + $key = is_null($r['mbid']) ? 'null' : $r['mbid']; + $id_array[$key] = $r['id']; + } // while + + /* Choose one */ + if (isset($id_array)) { + if ($mbid == '') { // Prefer null entry, otherwise pick the first + if (isset($id_array['null'])) { + $key = 'null'; + } + else { + $keys = array_keys($id_array); + $key = array_shift($keys); + } + $artist_id = $id_array[$key]; + $exists = true; + } + elseif (isset($id_array['null'])) { + $artist_id = $id_array['null']; + $exists = true; + if (!$readonly) { + $sql = "UPDATE `artist` SET `mbid`='$mbid' WHERE `id`='$artist_id'"; + $db_results = Dba::write($sql); + if (!$db_results) { + Error::add('general', sprintf(T_('Updating Artist: %s'), $artist)); + } + } + } + unset($id_array); + } + } // fuzzy matching + + /* If not found create */ + if (!$readonly && !$exists) { + + $prefix_txt = $prefix ? "'$prefix'" : 'NULL'; + + $mbid = $mbid == '' ? 'NULL' : "'$mbid'"; + + $sql = "INSERT INTO `artist` (`name`, `prefix`, `mbid`) " . + "VALUES ('$artist',$prefix_txt,$mbid)"; + $db_results = Dba::write($sql); + $artist_id = Dba::insert_id(); + + if (!$db_results) { + Error::add('general', sprintf(T_('Inserting Artist: %s'), $artist)); + } + + } // not found + // If readonly, and not found return false + elseif (!$exists) { + return false; + } + + self::$artists[$artist][$mbid] = $artist_id; + + return $artist_id; + + } // check_artist + + /** + * check_album + * Searches for album; if found returns id else inserts and returns id + */ + public static function check_album($album, $album_year = 0, + $album_disk = 0, $mbid = '', $readonly = false) { + + /* Clean up the values */ + $album = trim($album); + $album = Dba::escape($album); + // Not even sure if these can be negative, but better safe than + // llama. + $album_year = abs(intval($album_year)); + $album_disk = abs(intval($album_disk)); + + /* Ohh no the album has lost its mojo */ + if (!$album) { + $album = T_('Unknown (Orphaned)'); + unset($album_year, $album_disk); + } + + // Remove the prefix so we can sort it correctly + $trimmed = Catalog::trim_prefix($album); + $album = $trimmed['string']; + $prefix = $trimmed['prefix']; + + // Check to see if we've seen this album before + if (isset(self::$albums[$album][$album_year][$album_disk][$mbid])) { + return self::$albums[$album][$album_year][$album_disk][$mbid]; + } + + /* Set up the Query */ + $sql = "SELECT `id` FROM `album` WHERE `name` = '$album'" . + " AND `disk`='$album_disk' AND `year`='$album_year'" . + " AND `mbid`" . ($mbid ? "='$mbid'" : ' IS NULL') . + " AND `prefix`" . ($prefix ? "='$prefix'" : ' IS NULL'); + + $db_results = Dba::read($sql); + + /* If it's found */ + if ($r = Dba::fetch_assoc($db_results)) { + $album_id = $r['id']; + + // If we don't have art put it in the 'needs me some + // art' array + $art = new Art($r['id'], 'album'); + $art->get_db(); + if (!$art->raw) { + $key = $r['id']; + self::$_art_albums[$key] = $key; + } + + } //if found + elseif (!$readonly) { // If not found, create + + $prefix = $prefix ? "'$prefix'" : 'NULL'; + $mbid = $mbid ? "'$mbid'" : 'NULL'; + + $sql = "INSERT INTO `album` (`name`, `prefix`,`year`,`disk`,`mbid`) " . + "VALUES ('$album',$prefix,'$album_year','$album_disk',$mbid)"; + $db_results = Dba::write($sql); + $album_id = Dba::insert_id(); + + if (!$db_results) { + debug_event('album',"Error Unable to insert Album:$album",'2'); + return false; + } + + // Add it to the I needs me some album art array + self::$_art_albums[$album_id] = $album_id; + + } //not found + else { + // readonly and not found + return false; + } + + // Save the cache + self::$albums[$album][$album_year][$album_disk][$mbid] = $album_id; + + return $album_id; + + } // check_album + + /** + * check_tag + * This checks the tag we've been passed (name) + * and sees if it exists, and if so if it's mapped + * to this object, this is only done for songs for now + */ + public static function check_tag($value,$object_id,$object_type='song') { + + $map_id = Tag::add($object_type,$object_id,$value,'0'); + + return $map_id; + + } // check_tag + + /** + * check_title + * this checks to make sure something is + * set on the title, if it isn't it looks at the + * filename and trys to set the title based on that + */ + public static function check_title($title,$file=0) { + + if (strlen(trim($title)) < 1) { + $title = Dba::escape($file); + } + + return $title; + + } // check_title + + /** + * insert_local_song + * Insert a song that isn't already in the database this + * function is in here so we don't have to create a song object + */ + public function insert_local_song($file,$file_info) { + + /* Create the vainfo object and get info */ + $vainfo = new vainfo($file,'','','',$this->sort_pattern,$this->rename_pattern); + $vainfo->get_info(); + + $key = vainfo::get_tag_type($vainfo->tags); + + /* Clean Up the tags */ + $results = vainfo::clean_tag_info($vainfo->tags,$key,$file); + + /* Set the vars here... so we don't have to do the '" . $blah['asd'] . "' */ + $title = Dba::escape($results['title']); + $artist = $results['artist']; + $album = $results['album']; + $bitrate = $results['bitrate']; + $rate = $results['rate']; + $mode = $results['mode']; + $size = $results['size']; + $song_time = $results['time']; + $track = $results['track']; + $track_mbid = $results['mb_trackid']; + $album_mbid = $results['mb_albumid']; + $artist_mbid= $results['mb_artistid']; + $disk = $results['disk']; + $year = $results['year']; + $comment = $results['comment']; + $tags = $results['genre']; // multiple genre support makes this an array + $current_time = time(); + $lyrics = ' '; + + /* + * We have the artist/genre/album name need to check it in the tables + * If found then add & return id, else return id + */ + $artist_id = self::check_artist($artist,$artist_mbid); + $album_id = self::check_album($album,$year,$disk,$album_mbid); + $title = self::check_title($title,$file); + $add_file = Dba::escape($file); + + $sql = "INSERT INTO `song` (file,catalog,album,artist,title,bitrate,rate,mode,size,time,track,addition_time,year,mbid)" . + " VALUES ('$add_file','$this->id','$album_id','$artist_id','$title','$bitrate','$rate','$mode','$size','$song_time','$track','$current_time','$year','$track_mbid')"; + $db_results = Dba::write($sql); + + if (!$db_results) { + debug_event('insert',"Unable to insert $file -- $sql" . Dba::error(),'5','ampache-catalog'); + Error::add('catalog_add', sprintf(T_('SQL Error Adding %s'), $file)); + } + + $song_id = Dba::insert_id(); + + // multiple genre support + foreach ($tags as $tag) { + $tag = trim($tag); + self::check_tag($tag,$song_id); + self::check_tag($tag,$album_id,'album'); + self::check_tag($tag,$artist_id,'artist'); + } + + + /* Add the EXT information */ + $sql = "INSERT INTO `song_data` (`song_id`,`comment`,`lyrics`) " . + " VALUES ('$song_id','$comment','$lyrics')"; + $db_results = Dba::write($sql); + + if (!$db_results) { + debug_event('insert',"Unable to insert EXT Info for $file -- $sql",'5','ampache-catalog'); + } + + } // insert_local_song + + /** + * insert_remote_song + * takes the information gotten from XML-RPC and + * inserts it into the local database. The filename + * ends up being the url. + */ + public function insert_remote_song($song) { + + /* Limitations: + * Missing Following Metadata + * Disk,Rate + */ + + // Strip the SSID off of the url, we will need to regenerate this every time + $url = preg_replace("/ssid=.*&/","",$song['url']); + $title = Dba::escape($song['title']); + $album = self::check_album($song['album'],$song['year'],null,$song['album_mbid']); + $artist = self::check_artist($song['artist'],$song['artist_mbid']); + $bitrate = Dba::escape($song['bitrate']); + $size = Dba::escape($song['size']); + $song_time = Dba::escape($song['time']); + $track = Dba::escape($song['track']); + $year = Dba::escape($song['year']); + $title = Dba::escape($song['title']); + $mbid = Dba::escape($song['mbid']); + $mode = Dba::escape($song['mode']); + $current_time = time(); + $catalog_id = Dba::escape($this->id); + + $sql = "INSERT INTO `song` (`file`,`catalog`,`album`,`artist`,`title`,`bitrate`,`rate`,`mode`,`size`,`time`,`track`,`addition_time`,`year`,`mbid`)" . + " VALUES ('$url','$catalog_id','$album','$artist','$title','$bitrate','$rate','$mode','$size','$song_time','$track','$current_time','$year','$mbid')"; + $db_results = Dba::write($sql); + + if (!$db_results) { + debug_event('insert',"Unable to Add Remote $url -- $sql",'5','ampache-catalog'); + return false; + } + + // Weird to do this here, but we have the information - see if the album has art, if it doesn't then use the remote + // art url + $art = new Art($album, 'album'); + // If it doesn't have art... + if (!$art->get()) { + // Get the mime out + $get_vars = parse_url($song['art']); + $extension = substr($get_vars['query'],strlen($get_vars['query'])-3,3); + // Pull the image + $raw = Art::get_from_source( + array('url' => $song['art']), 'album'); + $inserted = $art->insert($raw,'image/' . $extension); + } + + return true; + + } // insert_remote_song + + /** + * insert_local_video + * This inserts a video file into the video file table the tag + * information we can get is super sketchy so it's kind of a crap shoot + * here + */ + public function insert_local_video($file,$filesize) { + + /* Create the vainfo object and get info */ + $vainfo = new vainfo($file,'','','',$this->sort_pattern,$this->rename_pattern); + $vainfo->get_info(); + + $tag_name = vainfo::get_tag_type($vainfo->tags); + $results = vainfo::clean_tag_info($vainfo->tags,$tag_name,$file); + + + $file = Dba::escape($file); + $catalog_id = Dba::escape($this->id); + $title = Dba::escape($results['title']); + $vcodec = $results['video_codec']; + $acodec = $results['audio_codec']; + $rezx = intval($results['resolution_x']); + $rezy = intval($results['resolution_y']); + $filesize = Dba::escape($filesize); + $time = Dba::escape($results['time']); + $mime = Dba::escape($results['mime']); + // UNUSED CURRENTLY + $comment = Dba::escape($results['comment']); + $year = Dba::escape($results['year']); + $disk = Dba::escape($results['disk']); + + $sql = "INSERT INTO `video` (`file`,`catalog`,`title`,`video_codec`,`audio_codec`,`resolution_x`,`resolution_y`,`size`,`time`,`mime`) " . + " VALUES ('$file','$catalog_id','$title','$vcodec','$acodec','$rezx','$rezy','$filesize','$time','$mime')"; + $db_results = Dba::write($sql); + + return true; + + } // insert_local_video + + /** + * check_remote_song + * checks to see if a remote song exists in the database or not + * if it find a song it returns the UID + */ + public function check_remote_song($url) { + + $url = Dba::escape($url); + + $sql = "SELECT `id` FROM `song` WHERE `file`='$url'"; + $db_results = Dba::read($sql); + + if ($results = Dba::fetch_assoc($db_results)) { + return $results['id']; + } + + return false; + + } // check_remote_song + + /** + * check_local_mp3 + * Checks the song to see if it's there already returns true if found, false if not + */ + public function check_local_mp3($full_file, $gather_type='') { + + $file_date = filemtime($full_file); + if ($file_date < $this->last_add) { + debug_event('Check','Skipping ' . $full_file . ' File modify time before last add run','3'); + return true; + } + + $full_file = Dba::escape($full_file); - $sql = "SELECT `id` FROM `song` WHERE `file` = '$full_file'"; - $db_results = Dba::read($sql); + $sql = "SELECT `id` FROM `song` WHERE `file` = '$full_file'"; + $db_results = Dba::read($sql); - //If it's found then return true - if (Dba::fetch_row($db_results)) { - return true; - } + //If it's found then return true + if (Dba::fetch_row($db_results)) { + return true; + } - return false; + return false; - } //check_local_mp3 + } //check_local_mp3 - /** - * import_m3u - * this takes m3u filename and then attempts to create a Public Playlist based on the filenames - * listed in the m3u - */ - public function import_m3u($filename) { + /** + * import_m3u + * this takes m3u filename and then attempts to create a Public Playlist based on the filenames + * listed in the m3u + */ + public function import_m3u($filename) { - $m3u_handle = fopen($filename,'r'); + $m3u_handle = fopen($filename,'r'); - $data = fread($m3u_handle,filesize($filename)); + $data = fread($m3u_handle,filesize($filename)); - $results = explode("\n",$data); + $results = explode("\n",$data); - $pattern = '/\.(' . Config::get('catalog_file_pattern') . ')$/i'; + $pattern = '/\.(' . Config::get('catalog_file_pattern') . ')$/i'; - // Foreach what we're able to pull out from the file - foreach ($results as $value) { + // Foreach what we're able to pull out from the file + foreach ($results as $value) { - // Remove extra whitespace - $value = trim($value); - if (preg_match($pattern,$value)) { + // Remove extra whitespace + $value = trim($value); + if (preg_match($pattern,$value)) { - /* Translate from \ to / so basename works */ - $value = str_replace("\\","/",$value); - $file = basename($value); + /* Translate from \ to / so basename works */ + $value = str_replace("\\","/",$value); + $file = basename($value); - /* Search for this filename, cause it's a audio file */ - $sql = "SELECT `id` FROM `song` WHERE `file` LIKE '%" . Dba::escape($file) . "'"; - $db_results = Dba::read($sql); - $results = Dba::fetch_assoc($db_results); + /* Search for this filename, cause it's a audio file */ + $sql = "SELECT `id` FROM `song` WHERE `file` LIKE '%" . Dba::escape($file) . "'"; + $db_results = Dba::read($sql); + $results = Dba::fetch_assoc($db_results); - if (isset($results['id'])) { $songs[] = $results['id']; } + if (isset($results['id'])) { $songs[] = $results['id']; } - } // if it's a file - // Check to see if it's a url from this ampache instance - elseif (substr($value,0,strlen(Config::get('web_path'))) == Config::get('web_path')) { - $song_id = intval(Song::parse_song_url($value)); + } // if it's a file + // Check to see if it's a url from this ampache instance + elseif (substr($value,0,strlen(Config::get('web_path'))) == Config::get('web_path')) { + $song_id = intval(Song::parse_song_url($value)); - $sql = "SELECT COUNT(*) FROM `song` WHERE `id`='$song_id'"; - $db_results = Dba::read($sql); + $sql = "SELECT COUNT(*) FROM `song` WHERE `id`='$song_id'"; + $db_results = Dba::read($sql); - if (Dba::num_rows($db_results)) { - $songs[] = $song_id; - } + if (Dba::num_rows($db_results)) { + $songs[] = $song_id; + } - } // end if it's an http url + } // end if it's an http url - } // end foreach line + } // end foreach line - debug_event('m3u_parse', "Parsed $filename, found " . count($songs) . " songs", 5); - - if (count($songs)) { - $name = "M3U - " . basename($filename,'.m3u'); - $playlist_id = Playlist::create($name,'public'); - - if (!$playlist_id) { - return array( - 'success' => false, - 'error' => 'Failed to create playlist.', - ); - } - - /* Recreate the Playlist */ - $playlist = new Playlist($playlist_id); - $playlist->add_songs($songs, true); - - return array( - 'success' => true, - 'id' => $playlist_id, - 'count' => count($songs) - ); - } - - return array( - 'success' => false, - 'error' => 'No valid songs found in M3U.' - ); - - } // import_m3u - - /** - * delete - * Deletes the catalog and everything associated with it - * it takes the catalog id - */ - public static function delete($catalog_id) { - - $catalog_id = Dba::escape($catalog_id); - - // First remove the songs in this catalog - $sql = "DELETE FROM `song` WHERE `catalog` = '$catalog_id'"; - $db_results = Dba::write($sql); - - // Only if the previous one works do we go on - if (!$db_results) { return false; } - - $sql = "DELETE FROM `video` WHERE `catalog` = '$catalog_id'"; - $db_results = Dba::write($sql); - - if (!$db_results) { return false; } - - // Next Remove the Catalog Entry it's self - $sql = "DELETE FROM `catalog` WHERE `id` = '$catalog_id'"; - $db_results = Dba::write($sql); - - // Run the cleaners... - self::gc(); - - } // delete - - /** - * exports the catalog - * it exports all songs in the database to the given export type. - */ - public function export($type) { - - // Select all songs in catalog - if($this->id) { - $sql = 'SELECT `id` FROM `song` ' . - "WHERE `catalog`='$this->id' " . - 'ORDER BY `album`, `track`'; - } - else { - $sql = 'SELECT `id` FROM `song` ORDER BY `album`, `track`'; - } - $db_results = Dba::read($sql); - - switch ($type) { - case 'itunes': - echo xml_get_header('itunes'); - while ($results = Dba::fetch_assoc($db_results)) { - $song = new Song($results['id']); - $song->format(); - - $xml = array(); - $xml['key']= $results['id']; - $xml['dict']['Track ID']= intval($results['id']); - $xml['dict']['Name'] = $song->title; - $xml['dict']['Artist'] = $song->f_artist_full; - $xml['dict']['Album'] = $song->f_album_full; - $xml['dict']['Total Time'] = intval($song->time) * 1000; // iTunes uses milliseconds - $xml['dict']['Track Number'] = intval($song->track); - $xml['dict']['Year'] = intval($song->year); - $xml['dict']['Date Added'] = date("Y-m-d\TH:i:s\Z",$song->addition_time); - $xml['dict']['Bit Rate'] = intval($song->bitrate/1000); - $xml['dict']['Sample Rate'] = intval($song->rate); - $xml['dict']['Play Count'] = intval($song->played); - $xml['dict']['Track Type'] = "URL"; - $xml['dict']['Location'] = Song::play_url($song->id); - echo xml_from_array($xml, 1, 'itunes'); - // flush output buffer - } // while result - echo xml_get_footer('itunes'); - - break; - case 'csv': - echo "ID,Title,Artist,Album,Length,Track,Year,Date Added,Bitrate,Played,File\n"; - while ($results = Dba::fetch_assoc($db_results)) { - $song = new Song($results['id']); - $song->format(); - echo '"' . $song->id . '","' . - $song->title . '","' . - $song->f_artist_full . '","' . - $song->f_album_full .'","' . - $song->f_time . '","' . - $song->f_track . '","' . - $song->year .'","' . - date("Y-m-d\TH:i:s\Z", $song->addition_time) . '","' . - $song->f_bitrate .'","' . - $song->played . '","' . - $song->file . "\n"; - } - break; - } // end switch - - } // export + debug_event('m3u_parse', "Parsed $filename, found " . count($songs) . " songs", 5); + + if (count($songs)) { + $name = "M3U - " . basename($filename,'.m3u'); + $playlist_id = Playlist::create($name,'public'); + + if (!$playlist_id) { + return array( + 'success' => false, + 'error' => 'Failed to create playlist.', + ); + } + + /* Recreate the Playlist */ + $playlist = new Playlist($playlist_id); + $playlist->add_songs($songs, true); + + return array( + 'success' => true, + 'id' => $playlist_id, + 'count' => count($songs) + ); + } + + return array( + 'success' => false, + 'error' => 'No valid songs found in M3U.' + ); + + } // import_m3u + + /** + * delete + * Deletes the catalog and everything associated with it + * it takes the catalog id + */ + public static function delete($catalog_id) { + + $catalog_id = Dba::escape($catalog_id); + + // First remove the songs in this catalog + $sql = "DELETE FROM `song` WHERE `catalog` = '$catalog_id'"; + $db_results = Dba::write($sql); + + // Only if the previous one works do we go on + if (!$db_results) { return false; } + + $sql = "DELETE FROM `video` WHERE `catalog` = '$catalog_id'"; + $db_results = Dba::write($sql); + + if (!$db_results) { return false; } + + // Next Remove the Catalog Entry it's self + $sql = "DELETE FROM `catalog` WHERE `id` = '$catalog_id'"; + $db_results = Dba::write($sql); + + // Run the cleaners... + self::gc(); + + } // delete + + /** + * exports the catalog + * it exports all songs in the database to the given export type. + */ + public function export($type) { + + // Select all songs in catalog + if($this->id) { + $sql = 'SELECT `id` FROM `song` ' . + "WHERE `catalog`='$this->id' " . + 'ORDER BY `album`, `track`'; + } + else { + $sql = 'SELECT `id` FROM `song` ORDER BY `album`, `track`'; + } + $db_results = Dba::read($sql); + + switch ($type) { + case 'itunes': + echo xml_get_header('itunes'); + while ($results = Dba::fetch_assoc($db_results)) { + $song = new Song($results['id']); + $song->format(); + + $xml = array(); + $xml['key']= $results['id']; + $xml['dict']['Track ID']= intval($results['id']); + $xml['dict']['Name'] = $song->title; + $xml['dict']['Artist'] = $song->f_artist_full; + $xml['dict']['Album'] = $song->f_album_full; + $xml['dict']['Total Time'] = intval($song->time) * 1000; // iTunes uses milliseconds + $xml['dict']['Track Number'] = intval($song->track); + $xml['dict']['Year'] = intval($song->year); + $xml['dict']['Date Added'] = date("Y-m-d\TH:i:s\Z",$song->addition_time); + $xml['dict']['Bit Rate'] = intval($song->bitrate/1000); + $xml['dict']['Sample Rate'] = intval($song->rate); + $xml['dict']['Play Count'] = intval($song->played); + $xml['dict']['Track Type'] = "URL"; + $xml['dict']['Location'] = Song::play_url($song->id); + echo xml_from_array($xml, 1, 'itunes'); + // flush output buffer + } // while result + echo xml_get_footer('itunes'); + + break; + case 'csv': + echo "ID,Title,Artist,Album,Length,Track,Year,Date Added,Bitrate,Played,File\n"; + while ($results = Dba::fetch_assoc($db_results)) { + $song = new Song($results['id']); + $song->format(); + echo '"' . $song->id . '","' . + $song->title . '","' . + $song->f_artist_full . '","' . + $song->f_album_full .'","' . + $song->f_time . '","' . + $song->f_track . '","' . + $song->year .'","' . + date("Y-m-d\TH:i:s\Z", $song->addition_time) . '","' . + $song->f_bitrate .'","' . + $song->played . '","' . + $song->file . "\n"; + } + break; + } // end switch + + } // export } // end of catalog class |