PageRenderTime 50ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/usr/lib/perl5/PDLNA/ContentLibrary.pm

https://github.com/geuma/pDLNA
Perl | 1774 lines | 1357 code | 214 blank | 203 comment | 113 complexity | 883a7d74fd1ee7299497a13b0b6bfe56 MD5 | raw file
  1. package PDLNA::ContentLibrary;
  2. #
  3. # pDLNA - a perl DLNA media server
  4. # Copyright (C) 2010-2018 Stefan Heumader-Rainer <stefan@heumader.at>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. use strict;
  20. use warnings;
  21. use DBI;
  22. use Date::Format;
  23. use File::Basename;
  24. use File::Glob qw(bsd_glob);
  25. use File::MimeInfo;
  26. use PDLNA::Config;
  27. use PDLNA::Database;
  28. use PDLNA::FFmpeg;
  29. use PDLNA::Log;
  30. use PDLNA::Media;
  31. use PDLNA::Utils;
  32. sub index_directories_thread
  33. {
  34. PDLNA::Log::log('Starting PDLNA::ContentLibrary::index_directories_thread().', 1, 'library');
  35. while(1)
  36. {
  37. my $dbh = PDLNA::Database::connect();
  38. $dbh->{AutoCommit} = 0;
  39. my $timestamp_start = time();
  40. foreach my $directory (@{$CONFIG{'DIRECTORIES'}}) # we are not able to run this part in threads - since glob seems to be NOT thread safe
  41. {
  42. _process_directory(
  43. $dbh,
  44. {
  45. 'fullname' => $directory->{'path'},
  46. 'media_type' => $directory->{'type'},
  47. 'recursion' => $directory->{'recursion'},
  48. 'exclude_dirs' => $directory->{'exclude_dirs'},
  49. 'exclude_items' => $directory->{'exclude_items'},
  50. 'allow_playlists' => $directory->{'allow_playlists'},
  51. 'parent_id' => 0,
  52. },
  53. );
  54. }
  55. # my $i = 0;
  56. # foreach my $external (@{$CONFIG{'EXTERNALS'}})
  57. # {
  58. # add_file_to_db(
  59. # $dbh,
  60. # {
  61. # 'element' => $external->{'command'} || $external->{'streamurl'},
  62. # 'media_type' => $external->{'type'},
  63. # 'mime_type' => '', # need to determine
  64. # 'element_basename' => $external->{'name'},
  65. # 'element_dirname' => '', # set the directory to nothing - no parent
  66. # 'external' => 1,
  67. # 'sequence' => $i,
  68. # 'root' => 1,
  69. # },
  70. # );
  71. # $i++;
  72. # }
  73. $dbh->commit();
  74. my $timestamp_end = time();
  75. # update our timestamp when finished
  76. PDLNA::Database::update_db(
  77. $dbh,
  78. {
  79. 'query' => 'UPDATE metadata SET value = ? WHERE param = ?',
  80. 'parameters' => [ $timestamp_end, 'TIMESTAMP', ],
  81. },
  82. );
  83. $dbh->commit();
  84. my $duration = $timestamp_end - $timestamp_start;
  85. PDLNA::Log::log('Indexing configured media directories took '.$duration.' seconds.', 1, 'library');
  86. my ($amount, $size) = get_amount_size_items_by($dbh, 'item_type', 1);
  87. PDLNA::Log::log('Configured media directories include '.$amount.' items with '.PDLNA::Utils::convert_bytes($size).' of size.', 1, 'library');
  88. _cleanup_contentlibrary($dbh);
  89. $dbh->commit();
  90. $timestamp_start = time();
  91. _fetch_media_attributes($dbh);
  92. $dbh->commit();
  93. $timestamp_end = time();
  94. $duration = $timestamp_end - $timestamp_start;
  95. PDLNA::Log::log('Getting media attributes for indexed media items took '.$duration.' seconds.', 1, 'library');
  96. PDLNA::Database::disconnect($dbh);
  97. sleep $CONFIG{'RESCAN_MEDIA'};
  98. }
  99. }
  100. sub process_directory
  101. {
  102. my $dbh = shift;
  103. my $params = shift;
  104. $$params{'path'} =~ s/\/$//;
  105. add_directory_to_db($dbh, $$params{'path'}, $$params{'rootdir'}, 0);
  106. $dbh->commit();
  107. $$params{'path'} = PDLNA::Utils::escape_brackets($$params{'path'});
  108. PDLNA::Log::log('Globbing directory: '.PDLNA::Utils::create_filesystem_path([ $$params{'path'}, '*', ]).'.', 2, 'library');
  109. my @elements = bsd_glob(PDLNA::Utils::create_filesystem_path([ $$params{'path'}, '*', ]));
  110. foreach my $element (sort @elements)
  111. {
  112. my $element_basename = basename($element);
  113. if (-d "$element" && $element =~ /lost\+found$/)
  114. {
  115. PDLNA::Log::log('Skipping '.$element.' directory.', 2, 'library');
  116. next;
  117. }
  118. elsif (-d "$element" && $$params{'recursion'} eq 'yes' && !grep(/^\Q$element_basename\E$/, @{$$params{'exclude_dirs'}}))
  119. {
  120. PDLNA::Log::log('Processing directory '.$element.'.', 2, 'library');
  121. process_directory(
  122. $dbh,
  123. {
  124. 'path' => $element,
  125. 'type' => $$params{'type'},
  126. 'recursion' => $$params{'recursion'},
  127. 'exclude_dirs' => $$params{'exclude_dirs'},
  128. 'exclude_items' => $$params{'exclude_items'},
  129. 'allow_playlists' => $$params{'allow_playlists'},
  130. 'rootdir' => 0,
  131. }
  132. );
  133. }
  134. elsif (-f "$element" && !grep(/^\Q$element_basename\E$/, @{$$params{'exclude_items'}}))
  135. {
  136. my $mime_type = mimetype($element);
  137. PDLNA::Log::log('Processing '.$element.' with MimeType '.$mime_type.'.', 2, 'library');
  138. if (PDLNA::Media::is_supported_mimetype($mime_type))
  139. {
  140. my $media_type = PDLNA::Media::return_type_by_mimetype($mime_type);
  141. if ($media_type && ($media_type eq $$params{'type'} || $$params{'type'} eq "all"))
  142. {
  143. PDLNA::Log::log('Adding '.$media_type.' element '.$element.'.', 2, 'library');
  144. my $fileid = add_file_to_db(
  145. $dbh,
  146. {
  147. 'element' => $element,
  148. 'media_type' => $media_type,
  149. 'mime_type' => $mime_type,
  150. 'element_basename' => $element_basename,
  151. 'element_dirname' => dirname($element),
  152. 'external' => 0,
  153. 'root' => 0,
  154. },
  155. );
  156. if ($media_type eq 'video')
  157. {
  158. my $tmp = $1 if $element =~ /^(.+)\.\w{3,4}$/;
  159. foreach my $extension ('srt')
  160. {
  161. if (-f $tmp.'.'.$extension)
  162. {
  163. my $subtitle_mimetype = mimetype($tmp.'.'.$extension);
  164. if (PDLNA::Media::is_supported_subtitle($subtitle_mimetype))
  165. {
  166. add_subtitle_to_db(
  167. $dbh,
  168. {
  169. 'file_id' => $fileid,
  170. 'path' => $tmp.'.'.$extension,
  171. 'mimetype' => $subtitle_mimetype,
  172. 'type' => $extension,
  173. },
  174. );
  175. }
  176. }
  177. }
  178. }
  179. }
  180. }
  181. elsif (PDLNA::Media::is_supported_playlist($mime_type))
  182. {
  183. PDLNA::Log::log('Adding playlist '.$element.' as directory.', 2, 'library');
  184. add_directory_to_db($dbh, $element, $$params{'rootdir'}, 1);
  185. my @items = PDLNA::Media::parse_playlist($element, $mime_type);
  186. for (my $i = 0; $i < @items; $i++)
  187. {
  188. if (PDLNA::Media::is_supported_stream($items[$i]) && $CONFIG{'LOW_RESOURCE_MODE'} == 0)
  189. {
  190. add_file_to_db(
  191. $dbh,
  192. {
  193. 'element' => $items[$i],
  194. 'media_type' => '', # need to determine
  195. 'mime_type' => '', # need to determine
  196. 'element_basename' => $items[$i],
  197. 'element_dirname' => $element, # set the directory to the playlist file itself
  198. 'external' => 1,
  199. 'sequence' => $i,
  200. 'root' => 0,
  201. },
  202. );
  203. }
  204. else
  205. {
  206. unless (PDLNA::Utils::is_path_absolute($items[$i]))
  207. {
  208. $items[$i] = PDLNA::Utils::create_filesystem_path([ dirname($element), $items[$i], ]);
  209. }
  210. if (-f $items[$i])
  211. {
  212. my $mime_type = mimetype($items[$i]);
  213. my $media_type = PDLNA::Media::return_type_by_mimetype($mime_type);
  214. add_file_to_db(
  215. $dbh,
  216. {
  217. 'element' => $items[$i],
  218. 'media_type' => $media_type,
  219. 'mime_type' => $mime_type,
  220. 'element_basename' => basename($items[$i]),
  221. 'element_dirname' => $element, # set the directory to the playlist file itself
  222. 'external' => 0,
  223. 'sequence' => $i,
  224. 'root' => 0,
  225. },
  226. );
  227. }
  228. }
  229. }
  230. # delete not (any more) configured - media files from playlists
  231. my @results = ();
  232. PDLNA::Database::select_db(
  233. $dbh,
  234. {
  235. 'query' => 'SELECT ID, NAME, FULLNAME FROM FILES WHERE PATH = ?',
  236. 'parameters' => [ $element, ],
  237. },
  238. \@results,
  239. );
  240. foreach my $result (@results)
  241. {
  242. unless (grep(/^\Q$result->{NAME}\E$/, @items) || grep(/^\Q$result->{FULLNAME}\E$/, @items))
  243. {
  244. delete_all_by_itemid($dbh, $result->{ID});
  245. }
  246. }
  247. }
  248. else
  249. {
  250. PDLNA::Log::log('Element '.$element.' skipped. Unsupported MimeType '.$mime_type.'.', 2, 'library');
  251. }
  252. }
  253. else
  254. {
  255. PDLNA::Log::log('Element '.$element.' skipped. Inlcuded in ExcludeList.', 2, 'library');
  256. }
  257. }
  258. }
  259. sub add_directory_to_db
  260. {
  261. my $dbh = shift;
  262. my $path = shift;
  263. my $rootdir = shift;
  264. my $type = shift;
  265. # check if directoriy is in db
  266. my @results = ();
  267. PDLNA::Database::select_db(
  268. $dbh,
  269. {
  270. 'query' => 'SELECT ID FROM DIRECTORIES WHERE PATH = ?',
  271. 'parameters' => [ $path, ],
  272. },
  273. \@results,
  274. );
  275. unless (defined($results[0]->{ID}))
  276. {
  277. # add directory to database
  278. PDLNA::Database::insert_db(
  279. $dbh,
  280. {
  281. 'query' => 'INSERT INTO DIRECTORIES (NAME, PATH, DIRNAME, ROOT, TYPE) VALUES (?,?,?,?,?)',
  282. 'parameters' => [ basename($path), $path, dirname($path), $rootdir, $type ],
  283. },
  284. );
  285. PDLNA::Log::log('Added directory '.$path.' to ContentLibrary.', 2, 'library');
  286. }
  287. }
  288. sub add_subtitle_to_db
  289. {
  290. my $dbh = shift;
  291. my $params = shift;
  292. # check if file is in db
  293. my @results = ();
  294. PDLNA::Database::select_db(
  295. $dbh,
  296. {
  297. 'query' => 'SELECT id, date, size FROM subtitles WHERE fullname = ? AND fileid_ref = ? AND mime_type = ?',
  298. 'parameters' => [ $$params{'path'}, $$params{'file_id'}, $$params{'mimetype'}, ],
  299. },
  300. \@results,
  301. );
  302. my @fileinfo = stat($$params{'path'});
  303. if (defined($results[0]->{id}))
  304. {
  305. if ($results[0]->{size} != $fileinfo[7] || $results[0]->{date} != $fileinfo[9])
  306. {
  307. # update the datbase entry (something changed)
  308. PDLNA::Database::update_db(
  309. $dbh,
  310. {
  311. 'query' => 'UPDATE subtitles SET date = ?, size = ? WHERE id = ?;',
  312. 'parameters' => [ $fileinfo[9], $fileinfo[7], $results[0]->{ID}, ],
  313. },
  314. );
  315. }
  316. }
  317. else # element not in database
  318. {
  319. PDLNA::Database::insert_db(
  320. $dbh,
  321. {
  322. 'query' => 'INSERT INTO subtitles (fileid_ref, fullname, name, type, mime_type, date, size) VALUES (?,?,?,?,?,?,?)',
  323. 'parameters' => [ $$params{'file_id'}, $$params{'path'}, basename($$params{'path'}), $$params{'type'}, $$params{'mimetype'}, $fileinfo[9], $fileinfo[7], ],
  324. },
  325. );
  326. }
  327. }
  328. sub add_file_to_db
  329. {
  330. my $dbh = shift;
  331. my $params = shift;
  332. my @fileinfo = ();
  333. $fileinfo[9] = 0;
  334. $fileinfo[7] = 0;
  335. my $file_extension = '';
  336. if ($$params{'external'} == 0)
  337. {
  338. @fileinfo = stat($$params{'element'});
  339. $file_extension = $1 if $$params{'element'} =~ /(\w{3,4})$/;
  340. }
  341. $$params{'sequence'} = 0 if !defined($$params{'sequence'});
  342. # check if file is in db
  343. my @results = ();
  344. PDLNA::Database::select_db(
  345. $dbh,
  346. {
  347. 'query' => 'SELECT ID, DATE, SIZE, MIME_TYPE, PATH, SEQUENCE FROM FILES WHERE FULLNAME = ? AND PATH = ?',
  348. 'parameters' => [ $$params{'element'}, $$params{'element_dirname'}, ],
  349. },
  350. \@results,
  351. );
  352. if (defined($results[0]->{ID}))
  353. {
  354. if (
  355. $results[0]->{SIZE} != $fileinfo[7] ||
  356. $results[0]->{DATE} != $fileinfo[9] ||
  357. # $results[0]->{MIME_TYPE} ne $$params{'mime_type'} ||
  358. $results[0]->{SEQUENCE} != $$params{'sequence'}
  359. )
  360. {
  361. # update the datbase entry (something changed)
  362. PDLNA::Database::update_db(
  363. $dbh,
  364. {
  365. 'query' => 'UPDATE FILES SET DATE = ?, SIZE = ?, MIME_TYPE = ?, TYPE = ?, SEQUENCE = ? WHERE ID = ?;',
  366. 'parameters' => [ $fileinfo[9], $fileinfo[7], $$params{'mime_type'}, $$params{'media_type'}, $$params{'sequence'}, $results[0]->{ID} ],
  367. },
  368. );
  369. # set FILEINFO entry to INVALID data
  370. PDLNA::Database::update_db(
  371. $dbh,
  372. {
  373. 'query' => 'UPDATE FILEINFO SET VALID = ? WHERE FILEID_REF = ?;',
  374. 'parameters' => [ 0, $results[0]->{ID}, ],
  375. },
  376. );
  377. }
  378. }
  379. else
  380. {
  381. # insert file to db
  382. PDLNA::Database::insert_db(
  383. $dbh,
  384. {
  385. 'query' => 'INSERT INTO FILES (NAME, PATH, FULLNAME, FILE_EXTENSION, DATE, SIZE, MIME_TYPE, TYPE, EXTERNAL, ROOT, SEQUENCE) VALUES (?,?,?,?,?,?,?,?,?,?,?)',
  386. 'parameters' => [ $$params{'element_basename'}, $$params{'element_dirname'}, $$params{'element'}, $file_extension, $fileinfo[9], $fileinfo[7], $$params{'mime_type'}, $$params{'media_type'}, $$params{'external'}, $$params{'root'}, $$params{'sequence'}, ],
  387. },
  388. );
  389. # select ID of newly added element
  390. @results = ();
  391. PDLNA::Database::select_db(
  392. $dbh,
  393. {
  394. 'query' => 'SELECT ID FROM FILES WHERE FULLNAME = ? AND PATH = ?',
  395. 'parameters' => [ $$params{'element'}, $$params{'element_dirname'}, ],
  396. },
  397. \@results,
  398. );
  399. # insert entry to FILEINFO table
  400. PDLNA::Database::insert_db(
  401. $dbh,
  402. {
  403. 'query' => 'INSERT INTO FILEINFO (FILEID_REF, VALID) VALUES (?,?)',
  404. 'parameters' => [ $results[0]->{ID}, 0, ],
  405. },
  406. );
  407. }
  408. return $results[0]->{ID};
  409. }
  410. sub cleanup_contentlibrary
  411. {
  412. my $dbh = shift;
  413. PDLNA::Log::log('Started to remove non existant files.', 1, 'library');
  414. my @files = ();
  415. PDLNA::Database::select_db(
  416. $dbh,
  417. {
  418. 'query' => 'SELECT ID, FULLNAME FROM FILES WHERE EXTERNAL = 0',
  419. 'parameters' => [ ],
  420. },
  421. \@files,
  422. );
  423. foreach my $file (@files)
  424. {
  425. unless (-f "$file->{FULLNAME}")
  426. {
  427. delete_all_by_itemid($dbh, $file->{ID});
  428. }
  429. }
  430. my @directories = ();
  431. PDLNA::Database::select_db(
  432. $dbh,
  433. {
  434. 'query' => 'SELECT ID, PATH, TYPE FROM DIRECTORIES',
  435. 'parameters' => [ ],
  436. },
  437. \@directories,
  438. );
  439. foreach my $directory (@directories)
  440. {
  441. if (
  442. ($directory->{TYPE} == 0 && !-d "$directory->{PATH}") ||
  443. ($directory->{TYPE} == 1 && !-f "$directory->{PATH}")
  444. )
  445. {
  446. PDLNA::Database::delete_db(
  447. $dbh,
  448. {
  449. 'query' => 'DELETE FROM DIRECTORIES WHERE ID = ?',
  450. 'parameters' => [ $directory->{ID}, ],
  451. },
  452. );
  453. }
  454. }
  455. my @subtitles = ();
  456. PDLNA::Database::select_db(
  457. $dbh,
  458. {
  459. 'query' => 'SELECT id, fullname FROM subtitles',
  460. 'parameters' => [ ],
  461. },
  462. \@subtitles,
  463. );
  464. foreach my $subtitle (@subtitles)
  465. {
  466. unless (-f $subtitle->{fullname})
  467. {
  468. PDLNA::Database::delete_db(
  469. $dbh,
  470. {
  471. 'query' => 'DELETE FROM subtitles WHERE id = ?',
  472. 'parameters' => [ $subtitle->{id}, ],
  473. },
  474. );
  475. }
  476. }
  477. # delete not (any more) configured - directories from database
  478. my @rootdirs = ();
  479. get_subdirectories_by_id($dbh, 0, undef, undef, \@rootdirs);
  480. my @conf_directories = ();
  481. foreach my $directory (@{$CONFIG{'DIRECTORIES'}})
  482. {
  483. push(@conf_directories, $directory->{'path'});
  484. }
  485. foreach my $rootdir (@rootdirs)
  486. {
  487. unless (grep(/^$rootdir->{PATH}\/$/, @conf_directories))
  488. {
  489. delete_subitems_recursively($dbh, $rootdir->{ID});
  490. }
  491. }
  492. # delete not (any more) configured - external from database
  493. my @externals = ();
  494. get_subfiles_by_id($dbh, 0, undef, undef, \@externals);
  495. my @conf_externals = ();
  496. foreach my $external (@{$CONFIG{'EXTERNALS'}})
  497. {
  498. push(@conf_externals, $external->{'name'});
  499. }
  500. foreach my $external (@externals)
  501. {
  502. unless (grep(/^$external->{NAME}$/, @conf_externals))
  503. {
  504. delete_all_by_itemid($dbh, $external->{ID});
  505. }
  506. }
  507. # delete external media items from database, if LOW_RESOURCE_MODE has been enabled
  508. if ($CONFIG{'LOW_RESOURCE_MODE'} == 1)
  509. {
  510. my @externalfiles = ();
  511. PDLNA::Database::select_db(
  512. $dbh,
  513. {
  514. 'query' => 'SELECT ID FROM FILES WHERE EXTERNAL = 1',
  515. 'parameters' => [ ],
  516. },
  517. \@externalfiles,
  518. );
  519. foreach my $externalfile (@externalfiles)
  520. {
  521. delete_all_by_itemid($dbh, $externalfile->{ID});
  522. }
  523. }
  524. foreach my $directory (@{$CONFIG{'DIRECTORIES'}})
  525. {
  526. # delete excluded directories and their items
  527. foreach my $excl_directory (@{$$directory{'exclude_dirs'}})
  528. {
  529. my @directories = ();
  530. PDLNA::Database::select_db(
  531. $dbh,
  532. {
  533. 'query' => 'SELECT ID FROM DIRECTORIES WHERE NAME = ? AND PATH LIKE ?',
  534. 'parameters' => [ $excl_directory, $directory->{'path'}.'%', ],
  535. },
  536. \@directories,
  537. );
  538. foreach my $dir (@directories)
  539. {
  540. delete_subitems_recursively($dbh, $dir->{ID});
  541. }
  542. }
  543. # delete excluded items
  544. foreach my $excl_items (@{$$directory{'exclude_items'}})
  545. {
  546. my @items = ();
  547. PDLNA::Database::select_db(
  548. $dbh,
  549. {
  550. 'query' => 'SELECT ID FROM FILES WHERE (NAME = ? AND PATH LIKE ?) OR (FULLNAME = ?)',
  551. 'parameters' => [ $excl_items, $directory->{'path'}.'%', $directory->{'path'}.$excl_items, ],
  552. },
  553. \@items,
  554. );
  555. foreach my $item (@items)
  556. {
  557. delete_all_by_itemid($dbh, $item->{ID});
  558. }
  559. }
  560. }
  561. # delete directories from database with no subdirectories or subfiles
  562. @directories = ();
  563. PDLNA::Database::select_db(
  564. $dbh,
  565. {
  566. 'query' => 'SELECT ID FROM DIRECTORIES',
  567. 'parameters' => [ ],
  568. },
  569. \@directories,
  570. );
  571. foreach my $dir (@directories)
  572. {
  573. my $amount = get_amount_elements_by_id($dbh, $dir->{ID});
  574. if ($amount == 0)
  575. {
  576. delete_subitems_recursively($dbh, $dir->{ID});
  577. }
  578. }
  579. }
  580. sub delete_all_by_itemid
  581. {
  582. my $dbh = shift;
  583. my $object_id = shift;
  584. PDLNA::Database::delete_db(
  585. $dbh,
  586. {
  587. 'query' => 'DELETE FROM FILES WHERE ID = ?',
  588. 'parameters' => [ $object_id, ],
  589. },
  590. );
  591. PDLNA::Database::delete_db(
  592. $dbh,
  593. {
  594. 'query' => 'DELETE FROM FILEINFO WHERE FILEID_REF = ?',
  595. 'parameters' => [ $object_id, ],
  596. },
  597. );
  598. PDLNA::Database::delete_db(
  599. $dbh,
  600. {
  601. 'query' => 'DELETE FROM subtitles WHERE fileid_ref = ?',
  602. 'parameters' => [ $object_id, ],
  603. },
  604. );
  605. }
  606. sub delete_subitems_recursively
  607. {
  608. my $dbh = shift;
  609. my $object_id = shift;
  610. my @subfiles = ();
  611. get_subfiles_by_id($dbh, $object_id, undef, undef, \@subfiles);
  612. foreach my $file (@subfiles)
  613. {
  614. delete_all_by_itemid($dbh, $file->{ID});
  615. }
  616. my @subdirs = ();
  617. get_subdirectories_by_id($dbh, $object_id, undef, undef, \@subdirs);
  618. foreach my $directory (@subdirs)
  619. {
  620. delete_subitems_recursively($dbh, $directory->{ID});
  621. PDLNA::Database::delete_db(
  622. $dbh,
  623. {
  624. 'query' => 'DELETE FROM DIRECTORIES WHERE ID = ?',
  625. 'parameters' => [ $directory->{ID}, ],
  626. },
  627. );
  628. }
  629. PDLNA::Database::delete_db(
  630. $dbh,
  631. {
  632. 'query' => 'DELETE FROM DIRECTORIES WHERE ID = ?',
  633. 'parameters' => [ $object_id, ],
  634. },
  635. );
  636. }
  637. sub get_fileinfo
  638. {
  639. my $dbh = shift;
  640. PDLNA::Log::log('Started to fetch metadata for media items.', 1, 'library');
  641. my @results = ();
  642. PDLNA::Database::select_db(
  643. $dbh,
  644. {
  645. 'query' => 'SELECT FILEID_REF FROM FILEINFO WHERE VALID = ?',
  646. 'parameters' => [ 0, ],
  647. },
  648. \@results,
  649. );
  650. my $counter = 0;
  651. foreach my $id (@results)
  652. {
  653. my @file = ();
  654. PDLNA::Database::select_db(
  655. $dbh,
  656. {
  657. 'query' => 'SELECT FULLNAME, TYPE, MIME_TYPE, EXTERNAL FROM FILES WHERE ID = ?',
  658. 'parameters' => [ $id->{FILEID_REF}, ],
  659. },
  660. \@file,
  661. );
  662. if ($file[0]->{EXTERNAL})
  663. {
  664. my %info = ();
  665. PDLNA::FFmpeg::get_media_info($file[0]->{FULLNAME}, \%info);
  666. if (defined($info{MIME_TYPE}))
  667. {
  668. PDLNA::Database::update_db(
  669. $dbh,
  670. {
  671. 'query' => 'UPDATE FILES SET FILE_EXTENSION = ?, MIME_TYPE = ?, TYPE = ? WHERE ID = ?',
  672. 'parameters' => [ $info{FILE_EXTENSION}, $info{MIME_TYPE}, $info{TYPE}, $id->{FILEID_REF}, ],
  673. },
  674. );
  675. $file[0]->{TYPE} = $info{TYPE};
  676. $file[0]->{MIME_TYPE} = $info{MIME_TYPE};
  677. }
  678. else
  679. {
  680. PDLNA::Database::update_db(
  681. $dbh,
  682. {
  683. 'query' => 'UPDATE FILES SET FILE_EXTENSION = ? WHERE ID = ?',
  684. 'parameters' => [ 'unkn', $id->{FILEID_REF}, ],
  685. },
  686. );
  687. }
  688. }
  689. unless (defined($file[0]->{MIME_TYPE}))
  690. {
  691. next;
  692. }
  693. #
  694. # FILL METADATA OF IMAGES
  695. #
  696. if ($file[0]->{TYPE} eq 'image')
  697. {
  698. my ($width, $height) = PDLNA::Media::get_image_fileinfo($file[0]->{FULLNAME});
  699. PDLNA::Database::update_db(
  700. $dbh,
  701. {
  702. 'query' => 'UPDATE FILEINFO SET WIDTH = ?, HEIGHT = ?, VALID = ? WHERE FILEID_REF = ?',
  703. 'parameters' => [ $width, $height, 1, $id->{FILEID_REF}, ],
  704. },
  705. );
  706. next;
  707. }
  708. if ($CONFIG{'LOW_RESOURCE_MODE'} == 1)
  709. {
  710. next;
  711. }
  712. #
  713. # FILL FFmpeg DATA OF VIDEO OR AUDIO FILES
  714. #
  715. my %info = ();
  716. if ($file[0]->{TYPE} eq 'video' || $file[0]->{TYPE} eq 'audio')
  717. {
  718. PDLNA::FFmpeg::get_media_info($file[0]->{FULLNAME}, \%info);
  719. PDLNA::Database::update_db(
  720. $dbh,
  721. {
  722. 'query' => 'UPDATE FILEINFO SET WIDTH = ?, HEIGHT = ?, DURATION = ?, BITRATE = ?, CONTAINER = ?, AUDIO_CODEC = ?, VIDEO_CODEC = ? WHERE FILEID_REF = ?',
  723. 'parameters' => [ $info{WIDTH}, $info{HEIGHT}, $info{DURATION}, $info{BITRATE}, $info{CONTAINER}, $info{AUDIO_CODEC}, $info{VIDEO_CODEC}, $id->{FILEID_REF}, ],
  724. },
  725. );
  726. if (defined($info{TYPE}) && defined($info{MIME_TYPE}) && defined($info{FILE_EXTENSION}))
  727. {
  728. PDLNA::Database::update_db(
  729. $dbh,
  730. {
  731. 'query' => 'UPDATE FILES SET MIME_TYPE = ?, TYPE = ?, FILE_EXTENSION = ? WHERE ID = ?',
  732. 'parameters' => [ $info{MIME_TYPE}, $info{TYPE}, $info{FILE_EXTENSION}, $id->{FILEID_REF}, ],
  733. },
  734. );
  735. }
  736. if ($file[0]->{TYPE} eq 'video')
  737. {
  738. PDLNA::Database::update_db(
  739. $dbh,
  740. {
  741. 'query' => 'UPDATE FILEINFO SET VALID = ? WHERE FILEID_REF = ?',
  742. 'parameters' => [ 1, $id->{FILEID_REF}, ],
  743. },
  744. );
  745. }
  746. }
  747. #
  748. # FILL METADATA OF AUDIO FILES
  749. #
  750. if ($file[0]->{TYPE} eq 'audio' && defined($info{AUDIO_CODEC}))
  751. {
  752. my %audioinfo = (
  753. 'ARTIST' => '',
  754. 'ALBUM' => '',
  755. 'TRACKNUM' => '',
  756. 'TITLE' => '',
  757. 'GENRE' => '',
  758. 'YEAR' => '',
  759. );
  760. PDLNA::Media::get_audio_fileinfo($file[0]->{FULLNAME}, $info{AUDIO_CODEC}, \%audioinfo);
  761. PDLNA::Database::update_db(
  762. $dbh,
  763. {
  764. 'query' => 'UPDATE FILEINFO SET ARTIST = ?, ALBUM = ?, TITLE = ?, GENRE = ?, YEAR = ?, TRACKNUM = ?, VALID = ? WHERE FILEID_REF = ?',
  765. 'parameters' => [ $audioinfo{ARTIST}, $audioinfo{ALBUM}, $audioinfo{TITLE}, $audioinfo{GENRE}, $audioinfo{YEAR}, $audioinfo{TRACKNUM}, 1, $id->{FILEID_REF}, ],
  766. },
  767. );
  768. }
  769. $counter++;
  770. unless ($counter % 50) # after 50 files, we are doing a commit
  771. {
  772. $dbh->commit();
  773. }
  774. }
  775. }
  776. sub get_subdirectories_by_id
  777. {
  778. my $dbh = shift;
  779. my $object_id = shift;
  780. my $starting_index = shift;
  781. my $requested_count = shift;
  782. my $directory_elements = shift;
  783. my $sql_query = 'SELECT ID, NAME, PATH FROM DIRECTORIES WHERE ';
  784. my @sql_param = ();
  785. if ($object_id == 0)
  786. {
  787. $sql_query .= 'ROOT = 1';
  788. }
  789. else
  790. {
  791. $sql_query .= 'DIRNAME IN ( SELECT PATH FROM DIRECTORIES WHERE ID = ? )';
  792. push(@sql_param, $object_id);
  793. }
  794. $sql_query .= ' ORDER BY NAME';
  795. if (defined($starting_index) && defined($requested_count))
  796. {
  797. $sql_query .= ' LIMIT '.$starting_index.', '.$requested_count;
  798. }
  799. PDLNA::Database::select_db(
  800. $dbh,
  801. {
  802. 'query' => $sql_query,
  803. 'parameters' => \@sql_param,
  804. },
  805. $directory_elements,
  806. );
  807. }
  808. sub get_directory_type_by_id
  809. {
  810. my $dbh = shift;
  811. my $object_id = shift;
  812. my @results = ();
  813. PDLNA::Database::select_db(
  814. $dbh,
  815. {
  816. 'query' => 'SELECT TYPE FROM DIRECTORIES WHERE ID = ?',
  817. 'parameters' => [ $object_id, ],
  818. },
  819. );
  820. return $results[0]->{TYPE};
  821. }
  822. sub get_subfiles_by_id
  823. {
  824. my $dbh = shift;
  825. my $object_id = shift;
  826. my $starting_index = shift;
  827. my $requested_count = shift;
  828. my $file_elements = shift;
  829. my $sql_query = 'SELECT ID, NAME, SIZE, DATE FROM FILES WHERE ';
  830. my @sql_param = ();
  831. if ($object_id == 0)
  832. {
  833. $sql_query .= 'ROOT = 1';
  834. }
  835. else
  836. {
  837. $sql_query .= 'PATH IN ( SELECT PATH FROM DIRECTORIES WHERE ID = ? )';
  838. push(@sql_param, $object_id);
  839. }
  840. $sql_query .= ' ORDER BY SEQUENCE, NAME';
  841. if (defined($starting_index) && defined($requested_count))
  842. {
  843. $sql_query .= ' LIMIT '.$starting_index.', '.$requested_count;
  844. }
  845. PDLNA::Database::select_db(
  846. $dbh,
  847. {
  848. 'query' => $sql_query,
  849. 'parameters' => \@sql_param,
  850. },
  851. $file_elements,
  852. );
  853. }
  854. sub get_subfiles_size_by_id
  855. {
  856. my $dbh = shift;
  857. my $object_id = shift;
  858. my @result = ();
  859. PDLNA::Database::select_db(
  860. $dbh,
  861. {
  862. 'query' => 'SELECT SUM(SIZE) AS FULLSIZE FROM FILES WHERE PATH IN ( SELECT PATH FROM DIRECTORIES WHERE ID = ? )',
  863. 'parameters' => [ $object_id, ],
  864. },
  865. \@result,
  866. );
  867. return $result[0]->{FULLSIZE};
  868. }
  869. sub get_amount_subdirectories_by_id
  870. {
  871. my $dbh = shift;
  872. my $object_id = shift;
  873. my @directory_amount = ();
  874. my $sql_query = 'SELECT COUNT(ID) AS AMOUNT FROM DIRECTORIES WHERE ';
  875. my @sql_param = ();
  876. if ($object_id == 0)
  877. {
  878. $sql_query .= 'ROOT = 1';
  879. }
  880. else
  881. {
  882. $sql_query .= 'DIRNAME IN ( SELECT PATH FROM DIRECTORIES WHERE ID = ? )';
  883. push(@sql_param, $object_id);
  884. }
  885. PDLNA::Database::select_db(
  886. $dbh,
  887. {
  888. 'query' => $sql_query,
  889. 'parameters' => \@sql_param,
  890. },
  891. \@directory_amount,
  892. );
  893. return $directory_amount[0]->{AMOUNT};
  894. }
  895. sub get_amount_subfiles_by_id
  896. {
  897. my $dbh = shift;
  898. my $object_id = shift;
  899. my @files_amount = ();
  900. my $sql_query = 'SELECT COUNT(ID) AS AMOUNT FROM FILES WHERE PATH IN ( SELECT PATH FROM DIRECTORIES WHERE ID = ?)';
  901. my @sql_param = ( $object_id, );
  902. PDLNA::Database::select_db(
  903. $dbh,
  904. {
  905. 'query' => $sql_query,
  906. 'parameters' => \@sql_param,
  907. },
  908. \@files_amount,
  909. );
  910. return $files_amount[0]->{AMOUNT};
  911. }
  912. sub get_amount_elements_by_id
  913. {
  914. my $dbh = shift;
  915. my $object_id = shift;
  916. my $directory_amount = 0;
  917. $directory_amount += get_amount_subdirectories_by_id($dbh, $object_id);
  918. $directory_amount += get_amount_subfiles_by_id($dbh, $object_id);
  919. return $directory_amount;
  920. }
  921. sub get_parent_of_directory_by_id
  922. {
  923. my $dbh = shift;
  924. my $object_id = shift;
  925. my @directory_parent = ();
  926. PDLNA::Database::select_db(
  927. $dbh,
  928. {
  929. 'query' => 'SELECT ID FROM DIRECTORIES WHERE PATH IN ( SELECT DIRNAME FROM DIRECTORIES WHERE ID = ? )',
  930. 'parameters' => [ $object_id, ],
  931. },
  932. \@directory_parent,
  933. );
  934. $directory_parent[0]->{ID} = 0 if !defined($directory_parent[0]->{ID});
  935. return $directory_parent[0]->{ID};
  936. }
  937. sub get_parent_of_item_by_id
  938. {
  939. my $dbh = shift;
  940. my $object_id = shift;
  941. my @item_parent = ();
  942. PDLNA::Database::select_db(
  943. $dbh,
  944. {
  945. 'query' => 'SELECT ID FROM DIRECTORIES WHERE PATH IN ( SELECT PATH FROM FILES WHERE ID = ? )',
  946. 'parameters' => [ $object_id, ],
  947. },
  948. \@item_parent,
  949. );
  950. $item_parent[0]->{ID} = 0 if !defined($item_parent[0]->{ID});
  951. return $item_parent[0]->{ID};
  952. }
  953. #
  954. # HELPER FUNCTIONS
  955. #
  956. #
  957. # NEW: returns array with subtitle items based on their ref_id
  958. #
  959. sub get_subtitles_by_refid
  960. {
  961. my $dbh = shift;
  962. my $ref_id = shift;
  963. my @subtitles = ();
  964. PDLNA::Database::select_db(
  965. $dbh,
  966. {
  967. 'query' => 'SELECT id, media_type, fullname, file_extension FROM items WHERE item_type = ? AND ref_id = ?',
  968. 'parameters' => [ 2, $ref_id, ],
  969. },
  970. \@subtitles,
  971. );
  972. return @subtitles;
  973. }
  974. #
  975. # NEW: fills array reference with items based on their parent_id and their item_type
  976. #
  977. sub get_items_by_parentid
  978. {
  979. my $dbh = shift;
  980. my $item_id = shift;
  981. my $starting_index = shift;
  982. my $requested_count = shift;
  983. my $item_type = shift;
  984. my $elements = shift;
  985. my $sql_query = 'SELECT id, fullname, title, size, date FROM items WHERE parent_id = ? AND item_type = ? ORDER BY title';
  986. if (defined($starting_index) && defined($requested_count))
  987. {
  988. $sql_query .= ' LIMIT '.$starting_index.', '.$requested_count;
  989. }
  990. PDLNA::Database::select_db(
  991. $dbh,
  992. {
  993. 'query' => $sql_query,
  994. 'parameters' => [ $item_id, $item_type, ]
  995. },
  996. $elements,
  997. );
  998. }
  999. #
  1000. # NEW: returns specified DB columns of an item based on its id
  1001. #
  1002. sub get_item_by_id
  1003. {
  1004. my $dbh = shift;
  1005. my $item_id = shift;
  1006. my $dbfields = shift;
  1007. my @items = ();
  1008. # SANITY CHECK - otherwise we are VULNERABLE to SQL Injections (if somebody is able to manipulate dbfields values)
  1009. foreach my $dbfield (@{$dbfields})
  1010. {
  1011. unless ($dbfield =~ /^(parent_id|item_type|media_type|mime_type|fullname|title|file_extension|date|size|width|height|duration)$/)
  1012. {
  1013. PDLNA::Log::log('ERROR: Parameter '.$dbfield.' as DB column is NOT valid. Possible SQL Injection attempt.', 0, 'default');
  1014. return @items;
  1015. }
  1016. }
  1017. PDLNA::Database::select_db(
  1018. $dbh,
  1019. {
  1020. 'query' => 'SELECT '.join(', ', @{$dbfields}).' FROM items WHERE id = ?',
  1021. 'parameters' => [ $item_id ],
  1022. },
  1023. \@items,
  1024. );
  1025. return @items;
  1026. }
  1027. #
  1028. # NEW: returns amount and total_size of items based on their (parent_id|item_type|media_type)
  1029. #
  1030. sub get_amount_size_items_by
  1031. {
  1032. my $dbh = shift;
  1033. my $dbfield = shift;
  1034. my $dbvalue = shift;
  1035. # SANITY CHECK - otherwise we are VULNERABLE to SQL Injections (if somebody is able to manipulate dbfields values)
  1036. unless ($dbfield =~ /^(parent_id|item_type|media_type)$/)
  1037. {
  1038. PDLNA::Log::log('ERROR: Parameter '.$dbfield.' as DB column is NOT valid. Possible SQL Injection attempt.', 0, 'default');
  1039. return (0,0);
  1040. }
  1041. my @result = ();
  1042. PDLNA::Database::select_db(
  1043. $dbh,
  1044. {
  1045. 'query' => 'SELECT COUNT(id) AS amount, SUM(size) as size FROM items WHERE '.$dbfield.' = ?',
  1046. 'parameters' => [ $dbvalue, ],
  1047. },
  1048. \@result,
  1049. );
  1050. my ($amount, $size) = 0;
  1051. $amount = $result[0]->{amount} if $result[0]->{amount};
  1052. $size = $result[0]->{size} if $result[0]->{size};
  1053. return ($amount, $size);
  1054. }
  1055. #
  1056. # NEW: returns amount of items based on their parent_id and item_type
  1057. #
  1058. sub get_amount_items_by_parentid_n_itemtype
  1059. {
  1060. my $dbh = shift;
  1061. my $parent_id = shift;
  1062. my $item_type = shift;
  1063. my @result = ();
  1064. PDLNA::Database::select_db(
  1065. $dbh,
  1066. {
  1067. 'query' => 'SELECT COUNT(id) AS amount FROM items WHERE parent_id = ? AND item_type = ?',
  1068. 'parameters' => [ $parent_id, $item_type, ],
  1069. },
  1070. \@result,
  1071. );
  1072. return $result[0]->{amount} || 0;
  1073. }
  1074. #
  1075. # NEW: returns total duration of items
  1076. #
  1077. sub get_duration_items
  1078. {
  1079. my $dbh = shift;
  1080. my $duration = PDLNA::Database::select_db_field_int(
  1081. $dbh,
  1082. {
  1083. 'query' => 'SELECT SUM(duration) FROM items',
  1084. 'parameters' => [ ],
  1085. },
  1086. );
  1087. return $duration;
  1088. }
  1089. #
  1090. # NEW: returns the parent_id of an item by id
  1091. #
  1092. sub get_parentid_by_id
  1093. {
  1094. my $dbh = shift;
  1095. my $item_id = shift;
  1096. my $parent_id = PDLNA::Database::select_db_field_int(
  1097. $dbh,
  1098. {
  1099. 'query' => 'SELECT parent_id FROM items WHERE id = ?',
  1100. 'parameters' => [ $item_id, ],
  1101. },
  1102. );
  1103. return $parent_id;
  1104. }
  1105. #
  1106. # NEW: return true if an item_id is under a parent_id
  1107. #
  1108. sub is_itemid_under_parentid
  1109. {
  1110. my $dbh = shift;
  1111. my $parent_id = shift;
  1112. my $item_id = shift;
  1113. while ($item_id != 0)
  1114. {
  1115. return 1 if $parent_id eq $item_id;
  1116. $item_id = get_parentid_by_id($dbh, $item_id);
  1117. }
  1118. return 0;
  1119. }
  1120. #
  1121. # HELPER FUNCTIONS END
  1122. #
  1123. #
  1124. # INTERNAL HELPER FUNCTIONS
  1125. #
  1126. sub _process_directory
  1127. {
  1128. my $dbh = shift;
  1129. my $params = shift;
  1130. $$params{'fullname'} = PDLNA::Utils::delete_trailing_slash($$params{'fullname'});
  1131. my $directory_id = _add_directory_item($dbh, $params);
  1132. $dbh->commit();
  1133. $$params{'fullname'} = PDLNA::Utils::escape_brackets($$params{'fullname'});
  1134. PDLNA::Log::log('Globbing directory: '.PDLNA::Utils::create_filesystem_path([ $$params{'fullname'}, '*', ]).'.', 2, 'library');
  1135. my @items = bsd_glob(PDLNA::Utils::create_filesystem_path([ $$params{'fullname'}, '*', ]));
  1136. foreach my $item (sort @items)
  1137. {
  1138. my $item_basename = basename($item);
  1139. if (-d $item && $item =~ /lost\+found$/)
  1140. {
  1141. PDLNA::Log::log('Skipping '.$item.' directory.', 2, 'library');
  1142. }
  1143. elsif (-d $item && $$params{'recursion'} eq 'yes' && !grep(/^\Q$item_basename\E$/, @{$$params{'exclude_dirs'}}))
  1144. {
  1145. PDLNA::Log::log('Processing directory '.$item.'.', 2, 'library');
  1146. $$params{'parent_id'} = $directory_id;
  1147. $$params{'fullname'} = $item;
  1148. _process_directory($dbh, $params);
  1149. }
  1150. elsif (-f $item && !grep(/^\Q$item_basename\E$/, @{$$params{'exclude_items'}}))
  1151. {
  1152. my $mime_type = mimetype($item);
  1153. PDLNA::Log::log('Processing '.$item.' with MimeType '.$mime_type.'.', 2, 'library');
  1154. if (PDLNA::Media::is_supported_mimetype($mime_type))
  1155. {
  1156. my $media_type = PDLNA::Media::return_type_by_mimetype($mime_type);
  1157. if ($media_type && ($media_type eq $$params{'media_type'} || $$params{'media_type'} eq 'all'))
  1158. {
  1159. PDLNA::Log::log('Adding '.$media_type.' item '.$item.'.', 2, 'library');
  1160. my $item_id = _add_media_item(
  1161. $dbh,
  1162. {
  1163. 'fullname' => $item,
  1164. 'parent_id' => $directory_id,
  1165. 'item_type' => 1,
  1166. 'mime_type' => $mime_type,
  1167. 'media_type' => $media_type,
  1168. },
  1169. );
  1170. # subtitles for video files
  1171. if ($media_type eq 'video')
  1172. {
  1173. my $tmp = $1 if $item =~ /^(.+)\.\w{3,4}$/;
  1174. foreach my $extension ('srt')
  1175. {
  1176. if (-f $tmp.'.'.$extension)
  1177. {
  1178. my $subtitle_mimetype = mimetype($tmp.'.'.$extension);
  1179. if (PDLNA::Media::is_supported_subtitle($subtitle_mimetype))
  1180. {
  1181. my $subtitle_id = _add_media_item(
  1182. $dbh,
  1183. {
  1184. 'fullname' => $tmp.'.'.$extension,
  1185. 'parent_id' => $directory_id,
  1186. 'ref_id' => $item_id,
  1187. 'item_type' => 2,
  1188. 'mime_type' => $subtitle_mimetype,
  1189. 'media_type' => $extension,
  1190. },
  1191. );
  1192. }
  1193. }
  1194. }
  1195. }
  1196. }
  1197. }
  1198. elsif (PDLNA::Media::is_supported_playlist($mime_type))
  1199. {
  1200. PDLNA::Log::log('Adding playlist '.$item.' as directory.', 2, 'library');
  1201. # TODO playlists
  1202. }
  1203. else
  1204. {
  1205. PDLNA::Log::log('Item '.$item.' skipped. Unsupported MimeType '.$mime_type.'.', 2, 'library');
  1206. }
  1207. }
  1208. else
  1209. {
  1210. PDLNA::Log::log('Item '.$item.' skipped. Inlcuded in ExcludeList.', 2, 'library');
  1211. }
  1212. }
  1213. }
  1214. sub _add_media_item
  1215. {
  1216. my $dbh = shift;
  1217. my $params = shift;
  1218. # gather size and date for item
  1219. my @fileinfo = stat($$params{'fullname'});
  1220. my $file_extension = $1 if $$params{'fullname'} =~ /(\w{3,4})$/;
  1221. # select from database
  1222. my @results = ();
  1223. PDLNA::Database::select_db(
  1224. $dbh,
  1225. {
  1226. 'query' => 'SELECT id, size, date FROM items WHERE fullname = ?',
  1227. 'parameters' => [ $$params{'fullname'}, ],
  1228. },
  1229. \@results,
  1230. );
  1231. if (defined($results[0]->{id})) # check if item is already in db
  1232. {
  1233. if ($results[0]->{size} != $fileinfo[7] || $results[0]->{date} != $fileinfo[9]) # item has changed
  1234. {
  1235. # TODO - CHECK IF I'M WORKING
  1236. PDLNA::Database::update_db(
  1237. $dbh,
  1238. {
  1239. 'query' => 'UPDATE items SET media_attributes = ?, size = ?, date = ? WHERE id = ?',
  1240. 'parameters' => [ 0, $fileinfo[7], $fileinfo[9], $results[0]->{id}, ],
  1241. },
  1242. );
  1243. PDLNA::Log::log('Updated media item '.$$params{'fullname'}.' in ContentLibrary.', 2, 'library');
  1244. }
  1245. else # item has not changed
  1246. {
  1247. PDLNA::Log::log('No need to update media item '.$$params{'fullname'}.' in ContentLibrary.', 3, 'library');
  1248. }
  1249. }
  1250. else # item not in database
  1251. {
  1252. # add item to database
  1253. PDLNA::Database::insert_db(
  1254. $dbh,
  1255. {
  1256. 'query' => 'INSERT INTO items (parent_id, ref_id, item_type, media_type, mime_type, fullname, title, size, date, file_extension) VALUES (?,?,?,?,?,?,?,?,?,?)',
  1257. 'parameters' => [ $$params{'parent_id'}, $$params{'ref_id'}, $$params{'item_type'}, $$params{'media_type'}, $$params{'mime_type'}, $$params{'fullname'}, basename($$params{'fullname'}), $fileinfo[7], $fileinfo[9], $file_extension, ],
  1258. },
  1259. );
  1260. PDLNA::Log::log('Added media item '.$$params{'fullname'}.' to ContentLibrary.', 2, 'library');
  1261. }
  1262. return _get_itemid_by_fullname($dbh, $$params{'fullname'});
  1263. }
  1264. sub _add_directory_item
  1265. {
  1266. my $dbh = shift;
  1267. my $params = shift;
  1268. unless (_get_itemid_by_fullname($dbh, $$params{'fullname'})) # check if item is already in db
  1269. {
  1270. # add directory to database
  1271. PDLNA::Database::insert_db(
  1272. $dbh,
  1273. {
  1274. 'query' => 'INSERT INTO items (parent_id, fullname, title) VALUES (?,?,?)',
  1275. 'parameters' => [ $$params{'parent_id'}, $$params{'fullname'}, basename($$params{'fullname'}), ],
  1276. },
  1277. );
  1278. PDLNA::Log::log('Added directory item '.$$params{'fullname'}.' to ContentLibrary.', 2, 'library');
  1279. }
  1280. return _get_itemid_by_fullname($dbh, $$params{'fullname'});
  1281. }
  1282. sub _delete_item_by_id
  1283. {
  1284. my $dbh = shift;
  1285. my $item_id = shift;
  1286. PDLNA::Database::delete_db(
  1287. $dbh,
  1288. {
  1289. 'query' => 'DELETE FROM items WHERE id = ?',
  1290. 'parameters' => [ $item_id, ],
  1291. },
  1292. );
  1293. }
  1294. sub _get_itemid_by_fullname
  1295. {
  1296. my $dbh = shift;
  1297. my $fullname = shift;
  1298. my @results = ();
  1299. PDLNA::Database::select_db(
  1300. $dbh,
  1301. {
  1302. 'query' => 'SELECT id FROM items WHERE fullname = ?',
  1303. 'parameters' => [ $fullname, ],
  1304. },
  1305. \@results,
  1306. );
  1307. return $results[0]->{id} if defined($results[0]->{id});
  1308. return undef;
  1309. }
  1310. sub _cleanup_contentlibrary
  1311. {
  1312. my $dbh = shift;
  1313. PDLNA::Log::log('Started to clean up ContentLibrary.', 1, 'library');
  1314. #
  1315. # delete items, which aren't present any more (if any)
  1316. #
  1317. my @items = ();
  1318. PDLNA::Database::select_db(
  1319. $dbh,
  1320. {
  1321. 'query' => 'SELECT id, fullname, item_type FROM items',
  1322. 'parameters' => [ ],
  1323. },
  1324. \@items,
  1325. );
  1326. foreach my $item (@items)
  1327. {
  1328. if ($item->{item_type} == 0) # directory
  1329. {
  1330. unless (-d $item->{fullname})
  1331. {
  1332. _delete_item_by_id($dbh, $item->{id});
  1333. }
  1334. }
  1335. elsif ($item->{item_type} == 1) # file (audio, video, image)
  1336. {
  1337. unless (-f $item->{fullname})
  1338. {
  1339. _delete_item_by_id($dbh, $item->{id});
  1340. }
  1341. }
  1342. elsif ($item->{item_type} == 2) # subtitles
  1343. {
  1344. unless (-f $item->{fullname})
  1345. {
  1346. _delete_item_by_id($dbh, $item->{id});
  1347. }
  1348. }
  1349. }
  1350. #
  1351. # delete directory items, which aren't configured any more (if any)
  1352. #
  1353. @items = ();
  1354. get_items_by_parentid($dbh, 0, undef, undef, 0, \@items);
  1355. my @conf_directories = ();
  1356. foreach my $directory (@{$CONFIG{'DIRECTORIES'}})
  1357. {
  1358. push(@conf_directories, $directory->{'path'});
  1359. }
  1360. foreach my $item (@items)
  1361. {
  1362. unless (grep(/^$item->{fullname}$/, @conf_directories))
  1363. {
  1364. # TODO subitems
  1365. _delete_item_by_id($dbh, $item->{id});
  1366. }
  1367. }
  1368. #
  1369. # delete external items, if LOW_RESOURCE_MODE is enabled (if any)
  1370. #
  1371. if ($CONFIG{'LOW_RESOURCE_MODE'} == 1)
  1372. {
  1373. }
  1374. #
  1375. # delete external items, which aren't configured any more (if any)
  1376. #
  1377. #
  1378. # delete items, which are excluded (if any)
  1379. #
  1380. foreach my $directory (@{$CONFIG{'DIRECTORIES'}})
  1381. {
  1382. my $directory_id = _get_itemid_by_fullname($dbh, $directory->{'path'});
  1383. if (defined($directory_id))
  1384. {
  1385. foreach my $excl_directory (@{$$directory{'exclude_dirs'}})
  1386. {
  1387. @items = ();
  1388. PDLNA::Database::select_db(
  1389. $dbh,
  1390. {
  1391. 'query' => 'SELECT id, title FROM items WHERE title = ? AND item_type = ?',
  1392. 'parameters' => [ $excl_directory, 0, ],
  1393. },
  1394. \@items,
  1395. );
  1396. foreach my $item (@items)
  1397. {
  1398. if (is_itemid_under_parentid($dbh, $directory_id, $item->{id}))
  1399. {
  1400. # TODO subitems
  1401. _delete_item_by_id($dbh, $item->{id});
  1402. }
  1403. }
  1404. }
  1405. # TODO media items
  1406. foreach my $excl_item (@{$$directory{'exclude_items'}})
  1407. {
  1408. }
  1409. }
  1410. }
  1411. #
  1412. # delete directory items, which don't have any subitems (if any)
  1413. #
  1414. @items = ();
  1415. PDLNA::Database::select_db(
  1416. $dbh,
  1417. {
  1418. 'query' => 'SELECT id, fullname FROM items WHERE item_type = ?',
  1419. 'parameters' => [ 0, ],
  1420. },
  1421. \@items,
  1422. );
  1423. foreach my $item (@items)
  1424. {
  1425. my ($amount, undef) = get_amount_size_items_by($dbh, 'parent_id', $item->{id});
  1426. if ($amount == 0)
  1427. {
  1428. _delete_item_by_id($dbh, $item->{id});
  1429. }
  1430. }
  1431. }
  1432. sub _fetch_media_attributes
  1433. {
  1434. my $dbh = shift;
  1435. PDLNA::Log::log('Started to fetch attributes for media items.', 1, 'library');
  1436. my @items = ();
  1437. PDLNA::Database::select_db(
  1438. $dbh,
  1439. {
  1440. 'query' => 'SELECT id, media_type, fullname FROM items WHERE media_attributes = ? AND item_type = ?',
  1441. 'parameters' => [ 0, 1, ],
  1442. },
  1443. \@items,
  1444. );
  1445. my $counter = 0;
  1446. foreach my $item (@items)
  1447. {
  1448. #
  1449. # FILL METADATA OF IMAGES
  1450. #
  1451. if ($item->{media_type} eq 'image')
  1452. {
  1453. my ($width, $height) = PDLNA::Media::get_image_fileinfo($item->{fullname});
  1454. PDLNA::Database::update_db(
  1455. $dbh,
  1456. {
  1457. 'query' => 'UPDATE items SET width = ?, height = ?, media_attributes = ? WHERE id = ?',
  1458. 'parameters' => [ $width, $height, 1, $item->{id}, ],
  1459. },
  1460. );
  1461. next;
  1462. }
  1463. #
  1464. # IN LOW_RESOURCE_MODE WE ARE NOT GOING TO OPEN EVERY SINGLE FILE
  1465. #
  1466. if ($CONFIG{'LOW_RESOURCE_MODE'} == 1)
  1467. {
  1468. next;
  1469. }
  1470. #
  1471. # FILL FFmpeg DATA OF VIDEO OR AUDIO FILES
  1472. #
  1473. if ($item->{media_type} eq 'video' || $item->{media_type} eq 'audio')
  1474. {
  1475. my %info = ();
  1476. PDLNA::FFmpeg::get_media_info($item->{fullname}, \%info);
  1477. PDLNA::Database::update_db(
  1478. $dbh,
  1479. {
  1480. 'query' => 'UPDATE items SET width = ?, height = ?, duration = ?, media_attributes = ? WHERE id = ?',
  1481. 'parameters' => [ $info{WIDTH}, $info{HEIGHT}, $info{DURATION}, 1, $item->{id}, ],
  1482. },
  1483. );
  1484. }
  1485. $counter++;
  1486. unless ($counter % 50) # after 50 files, we are doing a commit
  1487. {
  1488. $dbh->commit();
  1489. }
  1490. }
  1491. }
  1492. #
  1493. # INTERNAL HELPER FUNCTIONS END
  1494. #
  1495. 1;
  1496. # if ($CONFIG{'SPECIFIC_VIEWS'})
  1497. # {
  1498. # $self->{DIRECTORIES}->{'A_A'} = PDLNA::ContentDirectory->new({
  1499. # 'type' => 'meta',
  1500. # 'name' => 'Audio sorted by Artist',
  1501. # 'id' => 'A_A',
  1502. # 'parent_id' => '',
  1503. # });
  1504. # $self->{DIRECTORIES}->{'A_F'} = PDLNA::ContentDirectory->new({
  1505. # 'type' => 'meta',
  1506. # 'name' => 'Audio sorted by Folder',
  1507. # 'id' => 'A_F',
  1508. # 'parent_id' => '',
  1509. # });
  1510. # $self->{DIRECTORIES}->{'A_G'} = PDLNA::ContentDirectory->new({
  1511. # 'type' => 'meta',
  1512. # 'name' => 'Audio sorted by Genre',
  1513. # 'id' => 'A_G',
  1514. # 'parent_id' => '',
  1515. # });
  1516. # $self->{DIRECTORIES}->{'A_M'} = PDLNA::ContentDirectory->new({ # moods: WTF (dynamic)
  1517. # 'type' => 'meta',
  1518. # 'name' => 'Audio sorted by Mood',
  1519. # 'id' => 'A_M',
  1520. # 'parent_id' => '',
  1521. # });
  1522. # $self->{DIRECTORIES}->{'A_T'} = PDLNA::ContentDirectory->new({
  1523. # 'type' => 'meta',
  1524. # 'name' => 'Audio sorted by Title (Alphabet)',
  1525. # 'id' => 'A_M',
  1526. # 'parent_id' => '',
  1527. # });
  1528. #
  1529. # $self->{DIRECTORIES}->{'I_F'} = PDLNA::ContentDirectory->new({
  1530. # 'type' => 'meta',
  1531. # 'name' => 'Images sorted by Folder',
  1532. # 'id' => 'I_F',
  1533. # 'parent_id' => '',
  1534. # });
  1535. # $self->{DIRECTORIES}->{'I_T'} = PDLNA::ContentDirectory->new({
  1536. # 'type' => 'meta',
  1537. # 'name' => 'Images sorted by Date',
  1538. # 'id' => 'I_T',
  1539. # 'parent_id' => '',
  1540. # });
  1541. #
  1542. # $self->{DIRECTORIES}->{'V_D'} = PDLNA::ContentDirectory->new({
  1543. # 'type' => 'meta',
  1544. # 'name' => 'Videos sorted by Date',
  1545. # 'id' => 'V_D',
  1546. # 'parent_id' => '',
  1547. # });
  1548. # $self->{DIRECTORIES}->{'V_F'} = PDLNA::ContentDirectory->new({
  1549. # 'type' => 'meta',
  1550. # 'name' => 'Videos sorted by Folder',
  1551. # 'id' => 'V_F',
  1552. # 'parent_id' => '',
  1553. # });
  1554. # $self->{DIRECTORIES}->{'V_T'} = PDLNA::ContentDirectory->new({
  1555. # 'type' => 'meta',
  1556. # 'name' => 'Videos sorted by Title (Alphabet)',
  1557. # 'id' => 'V_T',
  1558. # 'parent_id' => '',
  1559. # });
  1560. # }