MusicDir.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. <?php
  2. class NestedDirectoryException extends Exception { }
  3. class Application_Model_MusicDir
  4. {
  5. /**
  6. * @holds propel database object
  7. */
  8. private $_dir;
  9. public function __construct($dir)
  10. {
  11. $this->_dir = $dir;
  12. }
  13. public function getId()
  14. {
  15. return $this->_dir->getId();
  16. }
  17. public function getType()
  18. {
  19. return $this->_dir->getType();
  20. }
  21. public function setType($type)
  22. {
  23. $this->_dir->setType($type);
  24. }
  25. public function getDirectory()
  26. {
  27. return $this->_dir->getDirectory();
  28. }
  29. public function setDirectory($dir)
  30. {
  31. $this->_dir->setDirectory($dir);
  32. $this->_dir->save();
  33. }
  34. public function setExistsFlag($flag)
  35. {
  36. $this->_dir->setExists($flag);
  37. $this->_dir->save();
  38. }
  39. public function setWatchedFlag($flag)
  40. {
  41. $this->_dir->setWatched($flag);
  42. $this->_dir->save();
  43. }
  44. public function getWatchedFlag()
  45. {
  46. return $this->_dir->getWatched();
  47. }
  48. public function getExistsFlag()
  49. {
  50. return $this->_dir->getExists();
  51. }
  52. /**
  53. * There are 2 cases where this function can be called.
  54. * 1. When watched dir was removed
  55. * 2. When some dir was watched, but it was unmounted
  56. *
  57. * In case of 1, $userAddedWatchedDir should be true
  58. * In case of 2, $userAddedWatchedDir should be false
  59. *
  60. * When $userAddedWatchedDir is true, it will set "Watched" flag to false
  61. * otherwise, it will set "Exists" flag to true
  62. */
  63. public function remove($userAddedWatchedDir=true)
  64. {
  65. $music_dir_id = $this->getId();
  66. $sql = <<<SQL
  67. SELECT DISTINCT s.instance_id
  68. FROM cc_music_dirs AS md
  69. LEFT JOIN cc_files AS f ON f.directory = md.id
  70. RIGHT JOIN cc_schedule AS s ON s.file_id = f.id
  71. WHERE md.id = :musicDirId;
  72. SQL;
  73. $show_instances = Application_Common_Database::prepareAndExecute($sql,
  74. array( ':musicDirId' => $music_dir_id ), 'all' );
  75. // get all the files on this dir
  76. $sql = <<<SQL
  77. UPDATE cc_files
  78. SET file_exists = 'f'
  79. WHERE id IN
  80. (SELECT f.id
  81. FROM cc_music_dirs AS md
  82. LEFT JOIN cc_files AS f ON f.directory = md.id
  83. WHERE md.id = :musicDirId);
  84. SQL;
  85. $affected = Application_Common_Database::prepareAndExecute($sql,
  86. array( ':musicDirId' => $music_dir_id ), 'all');
  87. // set RemovedFlag to true
  88. if ($userAddedWatchedDir) {
  89. self::setWatchedFlag(false);
  90. } else {
  91. self::setExistsFlag(false);
  92. }
  93. //$res = $this->_dir->delete();
  94. foreach ($show_instances as $show_instance_row) {
  95. $temp_show = new Application_Model_ShowInstance($show_instance_row["instance_id"]);
  96. $temp_show->updateScheduledTime();
  97. }
  98. Application_Model_RabbitMq::PushSchedule();
  99. }
  100. /**
  101. * Checks if p_dir1 is the ancestor of p_dir2. Returns
  102. * true if it is the ancestor, false otherwise. Note that
  103. * /home/user is considered the ancestor of /home/user
  104. *
  105. * @param string $p_dir1
  106. * The potential ancestor directory.
  107. * @param string $p_dir2
  108. * The potential descendent directory.
  109. * @return boolean
  110. * Returns true if it is the ancestor, false otherwise.
  111. */
  112. private static function isAncestorDir($p_dir1, $p_dir2)
  113. {
  114. if (strlen($p_dir1) > strlen($p_dir2)) {
  115. return false;
  116. }
  117. return substr($p_dir2, 0, strlen($p_dir1)) == $p_dir1;
  118. }
  119. /**
  120. * Checks whether the path provided is a valid path. A valid path
  121. * is defined as not being nested within an existing watched directory,
  122. * or vice-versa. Throws a NestedDirectoryException if invalid.
  123. *
  124. * @param string $p_path
  125. * The path we want to validate
  126. * @return void
  127. */
  128. public static function isPathValid($p_path)
  129. {
  130. $dirs = self::getWatchedDirs();
  131. $dirs[] = self::getStorDir();
  132. foreach ($dirs as $dirObj) {
  133. $dir = $dirObj->getDirectory();
  134. $diff = strlen($dir) - strlen($p_path);
  135. if ($diff == 0) {
  136. if ($dir == $p_path) {
  137. throw new NestedDirectoryException(sprintf(_("%s is already watched."), $p_path));
  138. }
  139. } elseif ($diff > 0) {
  140. if (self::isAncestorDir($p_path, $dir)) {
  141. throw new NestedDirectoryException(sprintf(_("%s contains nested watched directory: %s"), $p_path, $dir));
  142. }
  143. } else { /* diff < 0*/
  144. if (self::isAncestorDir($dir, $p_path)) {
  145. throw new NestedDirectoryException(sprintf(_("%s is nested within existing watched directory: %s"), $p_path, $dir));
  146. }
  147. }
  148. }
  149. }
  150. /** There are 2 cases where this function can be called.
  151. * 1. When watched dir was added
  152. * 2. When some dir was watched, but it was unmounted somehow, but gets mounted again
  153. *
  154. * In case of 1, $userAddedWatchedDir should be true
  155. * In case of 2, $userAddedWatchedDir should be false
  156. *
  157. * When $userAddedWatchedDir is true, it will set "Removed" flag to false
  158. * otherwise, it will set "Exists" flag to true
  159. *
  160. * @param $nestedWatch - if true, bypass path check, and Watched to false
  161. **/
  162. public static function addDir($p_path, $p_type, $userAddedWatchedDir=true, $nestedWatch=false)
  163. {
  164. if (!is_dir($p_path)) {
  165. return array("code"=>2, "error"=>sprintf(_("%s is not a valid directory."), $p_path));
  166. }
  167. $real_path = Application_Common_OsPath::normpath($p_path)."/";
  168. if ($real_path != "/") {
  169. $p_path = $real_path;
  170. }
  171. $exist_dir = self::getDirByPath($p_path);
  172. if (is_null($exist_dir)) {
  173. $temp_dir = new CcMusicDirs();
  174. $dir = new Application_Model_MusicDir($temp_dir);
  175. } else {
  176. $dir = $exist_dir;
  177. }
  178. $dir->setType($p_type);
  179. $p_path = Application_Common_OsPath::normpath($p_path)."/";
  180. try {
  181. /* isPathValid() checks if path is a substring or a superstring of an
  182. * existing dir and if not, throws NestedDirectoryException */
  183. if (!$nestedWatch) {
  184. self::isPathValid($p_path);
  185. }
  186. if ($userAddedWatchedDir) {
  187. $dir->setWatchedFlag(true);
  188. } else {
  189. if ($nestedWatch) {
  190. $dir->setWatchedFlag(false);
  191. }
  192. $dir->setExistsFlag(true);
  193. }
  194. $dir->setDirectory($p_path);
  195. return array("code"=>0);
  196. } catch (NestedDirectoryException $nde) {
  197. $msg = $nde->getMessage();
  198. return array("code"=>1, "error"=>"$msg");
  199. } catch (Exception $e) {
  200. return array("code"=>1,
  201. "error" => sprintf(
  202. _("%s is already set as the current storage dir or in the".
  203. " watched folders list"),
  204. $p_path
  205. )
  206. );
  207. }
  208. }
  209. /** There are 2 cases where this function can be called.
  210. * 1. When watched dir was added
  211. * 2. When some dir was watched, but it was unmounted somehow, but gets mounted again
  212. *
  213. * In case of 1, $userAddedWatchedDir should be true
  214. * In case of 2, $userAddedWatchedDir should be false
  215. *
  216. * When $userAddedWatchedDir is true, it will set "Watched" flag to true
  217. * otherwise, it will set "Exists" flag to true
  218. **/
  219. public static function addWatchedDir($p_path, $userAddedWatchedDir=true, $nestedWatch=false)
  220. {
  221. $res = self::addDir($p_path, "watched", $userAddedWatchedDir, $nestedWatch);
  222. if ($res['code'] != 0) { return $res; }
  223. //convert "linked" files (Airtime <= 1.8.2) to watched files.
  224. $propel_link_dir = CcMusicDirsQuery::create()
  225. ->filterByType('link')
  226. ->findOne();
  227. //see if any linked files exist.
  228. if (isset($propel_link_dir)) {
  229. //newly added watched directory object
  230. $propel_new_watch = CcMusicDirsQuery::create()
  231. ->filterByDirectory(Application_Common_OsPath::normpath($p_path)."/")
  232. ->findOne();
  233. //any files of the deprecated "link" type.
  234. $link_files = CcFilesQuery::create()
  235. ->setFormatter(ModelCriteria::FORMAT_ON_DEMAND)
  236. ->filterByDbDirectory($propel_link_dir->getId())
  237. ->find();
  238. $newly_watched_dir = $propel_new_watch->getDirectory();
  239. foreach ($link_files as $link_file) {
  240. $link_filepath = $link_file->getDbFilepath();
  241. //convert "link" file into a watched file.
  242. if ((strlen($newly_watched_dir) < strlen($link_filepath)) && (substr($link_filepath, 0, strlen($newly_watched_dir)) === $newly_watched_dir)) {
  243. //get the filepath path not including the watched directory.
  244. $sub_link_filepath = substr($link_filepath, strlen($newly_watched_dir));
  245. $link_file->setDbDirectory($propel_new_watch->getId());
  246. $link_file->setDbFilepath($sub_link_filepath);
  247. $link_file->save();
  248. }
  249. }
  250. }
  251. $data = array();
  252. $data["directory"] = $p_path;
  253. Application_Model_RabbitMq::SendMessageToMediaMonitor("new_watch", $data);
  254. return $res;
  255. }
  256. public static function getDirByPK($pk)
  257. {
  258. $dir = CcMusicDirsQuery::create()->findPK($pk);
  259. $mus_dir = new Application_Model_MusicDir($dir);
  260. return $mus_dir;
  261. }
  262. public static function getDirByPath($p_path)
  263. {
  264. $dir = CcMusicDirsQuery::create()
  265. ->filterByDirectory($p_path)
  266. ->findOne();
  267. if ($dir == NULL) {
  268. return null;
  269. } else {
  270. $mus_dir = new Application_Model_MusicDir($dir);
  271. return $mus_dir;
  272. }
  273. }
  274. /**
  275. * Search and returns watched dirs
  276. *
  277. * @param $exists search condition with exists flag
  278. * @param $watched search condition with watched flag
  279. */
  280. public static function getWatchedDirs($exists=true, $watched=true)
  281. {
  282. $result = array();
  283. $dirs = CcMusicDirsQuery::create()
  284. ->filterByType("watched");
  285. if ($exists !== null) {
  286. $dirs = $dirs->filterByExists($exists);
  287. }
  288. if ($watched !== null) {
  289. $dirs = $dirs->filterByWatched($watched);
  290. }
  291. $dirs = $dirs->find();
  292. foreach ($dirs as $dir) {
  293. $result[] = new Application_Model_MusicDir($dir);
  294. }
  295. return $result;
  296. }
  297. public static function getStorDir()
  298. {
  299. $dir = CcMusicDirsQuery::create()
  300. ->filterByType("stor")
  301. ->findOne();
  302. $mus_dir = new Application_Model_MusicDir($dir);
  303. return $mus_dir;
  304. }
  305. public static function setStorDir($p_dir)
  306. {
  307. // we want to be consistent when storing dir path.
  308. // path should always ends with trailing '/'
  309. $p_dir = Application_Common_OsPath::normpath($p_dir)."/";
  310. if (!is_dir($p_dir)) {
  311. return array("code"=>2, "error"=>sprintf(_("%s is not a valid directory."), $p_dir));
  312. } elseif (Application_Model_Preference::GetImportTimestamp()+10 > time()) {
  313. return array("code"=>3, "error"=>"Airtime is currently importing files. Please wait until this is complete before changing the storage directory.");
  314. }
  315. $dir = self::getStorDir();
  316. // if $p_dir doesn't exist in DB
  317. $exist = $dir->getDirByPath($p_dir);
  318. if ($exist == NULL) {
  319. $dir->setDirectory($p_dir);
  320. $dirId = $dir->getId();
  321. $data = array();
  322. $data["directory"] = $p_dir;
  323. $data["dir_id"] = $dirId;
  324. Application_Model_RabbitMq::SendMessageToMediaMonitor("change_stor", $data);
  325. return array("code"=>0);
  326. } else {
  327. return array("code"=>1,
  328. "error"=>sprintf(_("%s is already set as the current storage dir or in the watched folders list."), $p_dir));
  329. }
  330. }
  331. public static function getWatchedDirFromFilepath($p_filepath)
  332. {
  333. $dirs = CcMusicDirsQuery::create()
  334. ->filterByType(array("watched", "stor"))
  335. ->filterByExists(true)
  336. ->filterByWatched(true)
  337. ->find();
  338. foreach ($dirs as $dir) {
  339. $directory = $dir->getDirectory();
  340. if (substr($p_filepath, 0, strlen($directory)) === $directory) {
  341. $mus_dir = new Application_Model_MusicDir($dir);
  342. return $mus_dir;
  343. }
  344. }
  345. return null;
  346. }
  347. /** There are 2 cases where this function can be called.
  348. * 1. When watched dir was removed
  349. * 2. When some dir was watched, but it was unmounted
  350. *
  351. * In case of 1, $userAddedWatchedDir should be true
  352. * In case of 2, $userAddedWatchedDir should be false
  353. *
  354. * When $userAddedWatchedDir is true, it will set "Watched" flag to false
  355. * otherwise, it will set "Exists" flag to true
  356. **/
  357. public static function removeWatchedDir($p_dir, $userAddedWatchedDir=true)
  358. {
  359. //make sure that $p_dir has a trailing "/"
  360. $real_path = Application_Common_OsPath::normpath($p_dir)."/";
  361. if ($real_path != "/") {
  362. $p_dir = $real_path;
  363. }
  364. $dir = Application_Model_MusicDir::getDirByPath($p_dir);
  365. if (is_null($dir)) {
  366. return array("code"=>1, "error"=>sprintf(_("%s doesn't exist in the watched list."), $p_dir));
  367. } else {
  368. $dir->remove($userAddedWatchedDir);
  369. $data = array();
  370. $data["directory"] = $p_dir;
  371. Application_Model_RabbitMq::SendMessageToMediaMonitor("remove_watch", $data);
  372. return array("code"=>0);
  373. }
  374. }
  375. public static function splitFilePath($p_filepath)
  376. {
  377. $mus_dir = self::getWatchedDirFromFilepath($p_filepath);
  378. if (is_null($mus_dir)) {
  379. return null;
  380. }
  381. $length_dir = strlen($mus_dir->getDirectory());
  382. $fp = substr($p_filepath, $length_dir);
  383. return array($mus_dir->getDirectory(), trim($fp));
  384. }
  385. public function unhideFiles()
  386. {
  387. $files = $this->_dir->getCcFiless();
  388. $hid = 0;
  389. foreach ($files as $file) {
  390. $hid++;
  391. $file->setDbHidden(false);
  392. $file->save();
  393. }
  394. Logging::info("unhide '$hid' files");
  395. }
  396. }