1<?php 2/* 3 * vim:set softtabstop=4 shiftwidth=4 expandtab: 4 * 5 * LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later) 6 * Copyright 2001 - 2020 Ampache.org 7 * 8 * This program is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Affero General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Affero General Public License for more details. 17 * 18 * You should have received a copy of the GNU Affero General Public License 19 * along with this program. If not, see <https://www.gnu.org/licenses/>. 20 * 21 */ 22 23namespace Ampache\Module\Catalog; 24 25use Ampache\Config\AmpConfig; 26use Ampache\Module\Util\UtilityFactoryInterface; 27use Ampache\Repository\Model\Art; 28use Ampache\Repository\Model\Catalog; 29use Ampache\Repository\Model\Media; 30use Ampache\Repository\Model\Podcast_Episode; 31use Ampache\Repository\Model\Song; 32use Ampache\Repository\Model\Song_Preview; 33use Ampache\Repository\Model\Video; 34use Ampache\Module\System\AmpError; 35use Ampache\Module\System\Dba; 36use Ampache\Module\Util\Ui; 37use Ampache\Module\Util\VaInfo; 38use Exception; 39use Kunnu\Dropbox\DropboxApp; 40use Kunnu\Dropbox\Dropbox; 41use Kunnu\Dropbox\DropboxFile; 42use Kunnu\Dropbox\Exceptions\DropboxClientException; 43use ReflectionException; 44 45/** 46 * This class handles all actual work in regards to remote Dropbox catalogs. 47 */ 48class Catalog_dropbox extends Catalog 49{ 50 private $version = '000002'; 51 private $type = 'dropbox'; 52 private $description = 'Dropbox Remote Catalog'; 53 54 /** 55 * get_description 56 * This returns the description of this catalog 57 */ 58 public function get_description() 59 { 60 return $this->description; 61 } // get_description 62 63 /** 64 * get_version 65 * This returns the current version 66 */ 67 public function get_version() 68 { 69 return $this->version; 70 } // get_version 71 72 /** 73 * get_type 74 * This returns the current catalog type 75 */ 76 public function get_type() 77 { 78 return $this->type; 79 } // get_type 80 81 /** 82 * get_create_help 83 * This returns hints on catalog creation 84 */ 85 public function get_create_help() 86 { 87 return "<ul><li>" . T_("Go to https://www.dropbox.com/developers/apps/create") . "</li><li>" . T_("Select 'Dropbox API app'") . "</li><li>" . T_("Select 'Full Dropbox'") . "</li><li>" . T_("Give a name to your application and create it") . "</li><li>" . T_("Click the 'Generate' button to create an Access Token") . "</li><li>" . T_("Copy your App key and App secret and Access Token into the following fields.") . "</li></ul>"; 88 } // get_create_help 89 90 /** 91 * is_installed 92 * This returns true or false if remote catalog is installed 93 */ 94 public function is_installed() 95 { 96 $sql = "SHOW TABLES LIKE 'catalog_dropbox'"; 97 $db_results = Dba::query($sql); 98 99 return (Dba::num_rows($db_results) > 0); 100 } // is_installed 101 102 /** 103 * install 104 * This function installs the remote catalog 105 */ 106 public function install() 107 { 108 $collation = (AmpConfig::get('database_collation', 'utf8mb4_unicode_ci')); 109 $charset = (AmpConfig::get('database_charset', 'utf8mb4')); 110 $engine = ($charset == 'utf8mb4') ? 'InnoDB' : 'MYISAM'; 111 112 $sql = "CREATE TABLE `catalog_dropbox` (`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, `apikey` VARCHAR(255) COLLATE $collation NOT NULL, `secret` VARCHAR(255) COLLATE $collation NOT NULL, `path` VARCHAR(255) COLLATE $collation NOT NULL, `authtoken` VARCHAR(255) COLLATE $collation NOT NULL, `getchunk` TINYINT(1) NOT NULL, `catalog_id` INT(11) NOT NULL) ENGINE = $engine DEFAULT CHARSET=$charset COLLATE=$collation"; 113 Dba::query($sql); 114 115 return true; 116 } // install 117 118 /** 119 * @return array 120 */ 121 public function catalog_fields() 122 { 123 $fields = array(); 124 125 $fields['apikey'] = array('description' => T_('API key'), 'type' => 'text'); 126 $fields['secret'] = array('description' => T_('Secret'), 'type' => 'password'); 127 $fields['authtoken'] = array('description' => T_('Access Token'), 'type' => 'text'); 128 $fields['path'] = array('description' => T_('Path'), 'type' => 'text', 'value' => '/'); 129 $fields['getchunk'] = array( 130 'description' => T_('Get chunked files on analyze'), 131 'type' => 'checkbox', 132 'value' => true 133 ); 134 135 return $fields; 136 } 137 138 /** 139 * @return boolean 140 */ 141 public function isReady() 142 { 143 return (!empty($this->authtoken)); 144 } 145 146 public function show_ready_process() 147 { 148 // $this->showAuthToken(); 149 } 150 151 public function perform_ready() 152 { 153 // $this->authcode = $_REQUEST['authcode']; 154 // $this->completeAuthToken(); 155 } 156 157 public $apikey; 158 public $secret; 159 public $path; 160 public $authtoken; 161 public $getchunk; 162 163 /** 164 * Constructor 165 * 166 * Catalog class constructor, pulls catalog information 167 * @param integer $catalog_id 168 */ 169 public function __construct($catalog_id = null) 170 { 171 if ($catalog_id) { 172 $this->id = (int)$catalog_id; 173 $info = $this->get_info($catalog_id); 174 175 foreach ($info as $key => $value) { 176 $this->$key = $value; 177 } 178 } 179 } 180 181 /** 182 * create_type 183 * 184 * This creates a new catalog type entry for a catalog 185 * It checks to make sure its parameters is not already used before creating 186 * the catalog. 187 * @param $catalog_id 188 * @param array $data 189 * @return boolean 190 */ 191 public static function create_type($catalog_id, $data) 192 { 193 $apikey = trim($data['apikey']); 194 $secret = trim($data['secret']); 195 $authtoken = trim($data['authtoken']); 196 $path = $data['path']; 197 $getchunk = $data['getchunk']; 198 199 if (!strlen($apikey) || !strlen($secret) || !strlen($authtoken)) { 200 AmpError::add('general', T_('Error: API Key, Secret and Access Token Required for Dropbox Catalogs')); 201 202 return false; 203 } 204 try { 205 $app = new DropboxApp($apikey, $secret, $authtoken); 206 } catch (DropboxClientException $e) { 207 AmpError::add('general', T_('Invalid "API key", "secret", or "access token": ' . $e->getMessage())); 208 209 return false; 210 } 211 $dropbox = new Dropbox($app); 212 213 try { 214 $listFolderContents = $dropbox->listFolder($path); 215 } catch (DropboxClientException $e) { 216 AmpError::add('general', T_('Invalid "dropbox-path": ' . $e->getMessage())); 217 $listFolderContents = null; 218 219 return false; 220 } 221 222 // Make sure this catalog isn't already in use by an existing catalog 223 $sql = 'SELECT `id` FROM `catalog_dropbox` WHERE `apikey` = ?'; 224 $db_results = Dba::read($sql, array($apikey)); 225 226 if (Dba::num_rows($db_results)) { 227 debug_event('dropbox.catalog', 'Cannot add catalog with duplicate key ' . $apikey, 1); 228 AmpError::add('general', sprintf(T_('Error: Catalog with %s already exists'), $apikey)); 229 230 return false; 231 } 232 233 $sql = 'INSERT INTO `catalog_dropbox` (`apikey`, `secret`, `authtoken`, `path`, `getchunk`, `catalog_id`) VALUES (?, ?, ?, ?, ?, ?)'; 234 Dba::write($sql, array($apikey, $secret, $authtoken, $path, ($getchunk ? 1 : 0), $catalog_id)); 235 236 return true; 237 } 238 239 /** 240 * add_to_catalog 241 * this function adds new files to an 242 * existing catalog 243 * @param array $options 244 * @return boolean 245 */ 246 public function add_to_catalog($options = null) 247 { 248 // Prevent the script from timing out 249 set_time_limit(0); 250 251 if ($options != null) { 252 $this->authcode = $options['authcode']; 253 } 254 255 if (!defined('SSE_OUTPUT')) { 256 Ui::show_box_top(T_('Running Dropbox Remote Update') . '. . .'); 257 } 258 $this->update_remote_catalog(); 259 if (!defined('SSE_OUTPUT')) { 260 Ui::show_box_bottom(); 261 } 262 263 return true; 264 } // add_to_catalog 265 266 /** 267 * update_remote_catalog 268 * 269 * Pulls the data from a remote catalog and adds any missing songs to the 270 * database. 271 */ 272 public function update_remote_catalog() 273 { 274 $app = new DropboxApp($this->apikey, $this->secret, $this->authtoken); 275 $dropbox = new Dropbox($app); 276 $this->count = 0; 277 $this->add_files($dropbox, $this->path); 278 /* Update the Catalog last_add */ 279 $this->update_last_add(); 280 281 Ui::update_text('', sprintf(T_('Catalog Update Finished. Total Media: [%s]'), $this->count)); 282 283 return true; 284 } 285 286 /** 287 * add_files 288 * 289 * Recurses through directories and pulls out all media files 290 * @param $dropbox 291 * @param $path 292 */ 293 public function add_files($dropbox, $path) 294 { 295 debug_event('dropbox.catalog', "List contents for " . $path, 5); 296 $listFolderContents = $dropbox->listFolder($path, ['recursive' => true]); 297 298 // Fetch items on the first page 299 $items = $listFolderContents->getItems(); 300 foreach ($items as $item) { 301 if ($item->getDataProperty('.tag') == "file") { 302 $subpath = $item->getDataProperty('path_display'); 303 $this->add_file($dropbox, $subpath); 304 } 305 } 306 307 // Dropbox lists items in pages so you need to set your current 308 // position then re-fetch the list from that cursor position. 309 if ($listFolderContents->hasMoreItems()) { 310 do { 311 $cursor = $listFolderContents->getCursor(); 312 $listFolderContinue = $dropbox->listFolderContinue($cursor); 313 $remainingItems = $listFolderContinue->getItems(); 314 foreach ($remainingItems as $item) { 315 if ($item->getDataProperty('.tag') == "file") { 316 $subpath = $item->getDataProperty('path_display'); 317 $this->add_file($dropbox, $subpath); 318 } 319 } 320 } while ($listFolderContinue->hasMoreItems() == true); 321 } 322 } 323 324 /** 325 * @param $dropbox 326 * @param $path 327 * @return boolean 328 */ 329 public function add_file($dropbox, $path) 330 { 331 $file = $dropbox->getMetadata($path, ["include_media_info" => true, "include_deleted" => true]); 332 $filesize = $file->getDataProperty('size'); 333 if ($filesize > 0) { 334 $is_audio_file = Catalog::is_audio_file($path); 335 $is_video_file = Catalog::is_video_file($path); 336 337 if ($is_audio_file) { 338 if (count($this->get_gather_types('music')) > 0) { 339 $this->insert_song($dropbox, $path); 340 } else { 341 debug_event('dropbox.catalog', "read " . $path . " ignored, bad media type for this catalog.", 5); 342 343 return false; 344 } 345 } else { 346 if (count($this->get_gather_types('video')) > 0) { 347 if ($is_video_file) { 348 $this->insert_video($dropbox, $path); 349 } else { 350 debug_event('dropbox.catalog', 351 "read " . $path . " ignored, bad media type for this video catalog.", 5); 352 353 return false; 354 } 355 } 356 } 357 } else { 358 debug_event('dropbox.catalog', "read " . $path . " ignored, 0 bytes", 5); 359 } 360 361 return true; 362 } 363 364 /** 365 * _insert_local_song 366 * 367 * Insert a song that isn't already in the database. 368 * @param $dropbox 369 * @param $path 370 * @return boolean 371 * @throws DropboxClientException|Exception 372 */ 373 private function insert_song($dropbox, $path) 374 { 375 if ($this->check_remote_file($path)) { 376 debug_event('dropbox_catalog', 'Skipping existing song ' . $path, 5); 377 } else { 378 $readfile = true; 379 $meta = $dropbox->getMetadata($path); 380 $outfile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $meta->getName(); 381 382 // Download File 383 $this->download($dropbox, $path, -1, $outfile); 384 385 $vainfo = $this->getUtilityFactory()->createVaInfo( 386 $outfile, 387 $this->get_gather_types('music'), 388 '', 389 '', 390 $this->sort_pattern, 391 $this->rename_pattern, 392 $readfile 393 ); 394 $vainfo->get_info(); 395 396 $key = VaInfo::get_tag_type($vainfo->tags); 397 $results = VaInfo::clean_tag_info($vainfo->tags, $key, $outfile); 398 // Set the remote path 399 $results['file'] = $path; 400 $results['catalog'] = $this->id; 401 402 // Set the remote path 403 if (!empty($results['artist']) && !empty($results['album'])) { 404 $this->count++; 405 $results['file'] = $outfile; 406 $song_id = Song::insert($results); 407 if ($song_id) { 408 parent::gather_art([$song_id]); 409 } 410 $results['file'] = $path; 411 $sql = "UPDATE `song` SET `file` = ? WHERE `id` = ?"; 412 Dba::write($sql, array($results['file'], $song_id)); 413 } else { 414 debug_event('dropbox.catalog', 415 $results['file'] . " ignored because it is an orphan songs. Please check your catalog patterns.", 416 5); 417 } 418 unlink($outfile); 419 420 return true; 421 } 422 423 return false; 424 } 425 426 /** 427 * insert_local_video 428 * This inserts a video file into the video file table the tag 429 * information we can get is super sketchy so it's kind of a crap shoot 430 * here 431 * @param $dropbox 432 * @param $path 433 * @return integer 434 * @throws DropboxClientException|Exception 435 */ 436 public function insert_video($dropbox, $path) 437 { 438 if ($this->check_remote_file($path)) { 439 debug_event('dropbox_catalog', 'Skipping existing song ' . $path, 5); 440 } else { 441 /* Create the vainfo object and get info */ 442 $readfile = true; 443 $meta = $dropbox->getMetadata($path); 444 $outfile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $meta->getName(); 445 446 // Download File 447 $res = $this->download($dropbox, $path, 40960, $outfile); 448 449 if ($res) { 450 $gtypes = $this->get_gather_types('video'); 451 452 $vainfo = $this->getUtilityFactory()->createVaInfo( 453 $outfile, 454 $gtypes, 455 '', 456 '', 457 $this->sort_pattern, 458 $this->rename_pattern, 459 $readfile 460 ); 461 $vainfo->get_info(); 462 463 $tag_name = VaInfo::get_tag_type($vainfo->tags, 'metadata_order_video'); 464 $results = VaInfo::clean_tag_info($vainfo->tags, $tag_name, $outfile); 465 $results['catalog'] = $this->id; 466 467 $results['file'] = $outfile; 468 $video_id = Video::insert($results, $gtypes, []); 469 if ($results['art']) { 470 $art = new Art($video_id, 'video'); 471 $art->insert_url($results['art']); 472 473 if (AmpConfig::get('generate_video_preview')) { 474 Video::generate_preview($video_id); 475 } 476 } else { 477 $this->videos_to_gather[] = $video_id; 478 } 479 $results['file'] = $path; 480 $sql = "UPDATE `video` SET `file` = ? WHERE `id` = ?"; 481 Dba::write($sql, array($results['file'], $video_id)); 482 483 return $video_id; 484 } else { 485 debug_event('dropbox.catalog', 'failed to download file', 5, 'ampache-catalog'); 486 } 487 } // insert_video 488 489 return 0; 490 } 491 492 /** 493 * @param $dropbox 494 * @param $path 495 * @param $maxlen 496 * @param $dropboxFile 497 * @return boolean 498 * @throws DropboxClientException 499 */ 500 public function download($dropbox, $path, $maxlen, $dropboxFile = null) 501 { 502 // Path cannot be null 503 if (is_null($path)) { 504 throw new DropboxClientException("Path cannot be null."); 505 } 506 507 // Make Dropbox File if target is specified 508 $dropboxFile = $dropboxFile ? $dropbox->makeDropboxFile($dropboxFile, $maxlen, null, 509 DropboxFile::MODE_WRITE) : null; 510 511 // Download File 512 $response = $dropbox->postToContent('/files/download', ['path' => $path], null, $dropboxFile); 513 if ($response->getHttpStatusCode() == 200) { 514 return true; 515 } 516 517 return false; 518 } 519 520 /** 521 * @return array 522 * @throws ReflectionException 523 */ 524 public function verify_catalog_proc() 525 { 526 $updated = array('total' => 0, 'updated' => 0); 527 528 set_time_limit(0); 529 530 $utilityFactory = $this->getUtilityFactory(); 531 $app = new DropboxApp($this->apikey, $this->secret, $this->authtoken); 532 $dropbox = new Dropbox($app); 533 try { 534 $sql = 'SELECT `id`, `file`, `title` FROM `song` WHERE `catalog` = ?'; 535 $db_results = Dba::read($sql, array($this->id)); 536 while ($row = Dba::fetch_assoc($db_results)) { 537 $updated['total']++; 538 debug_event('dropbox.catalog', 'Starting verify on ' . $row['file'] . '(' . $row['id'] . ')', 5, 539 'ampache-catalog'); 540 $path = $row['file']; 541 $readfile = true; 542 $filesize = 40960; 543 $meta = $dropbox->getMetadata($path); 544 $outfile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $meta->getName(); 545 546 $res = $this->download($dropbox, $path, $filesize, $outfile); 547 if ($res) { 548 debug_event('dropbox.catalog', 'updating song', 5, 'ampache-catalog'); 549 $song = new Song($row['id']); 550 551 $vainfo = $utilityFactory->createVaInfo( 552 $outfile, 553 $this->get_gather_types('music'), 554 '', 555 '', 556 $this->sort_pattern, 557 $this->rename_pattern, 558 $readfile 559 ); 560 $vainfo->forceSize($filesize); 561 $vainfo->get_info(); 562 563 $key = VaInfo::get_tag_type($vainfo->tags); 564 $results = VaInfo::clean_tag_info($vainfo->tags, $key, $outfile); 565 // Must compare to original path, not temporary location. 566 $results['file'] = $path; 567 $info = ($song->id) ? self::update_song_from_tags($results, $song) : array(); 568 if ($info['change']) { 569 Ui::update_text('', sprintf(T_('Updated song: "%s"'), $row['title'])); 570 $updated['updated']++; 571 } else { 572 Ui::update_text('', sprintf(T_('Song up to date: "%s"'), $row['title'])); 573 } 574 } else { 575 debug_event('dropbox.catalog', 'removing song', 5, 'ampache-catalog'); 576 Ui::update_text('', sprintf(T_('Removing song: "%s"'), $row['title'])); 577 Dba::write('DELETE FROM `song` WHERE `id` = ?', array($row['id'])); 578 } 579 } 580 581 $this->update_last_update(); 582 } catch (DropboxClientException $e) { 583 AmpError::add('general', T_('Invalid "API key", "secret", or "access token": ' . $e->getMessage())); 584 } 585 586 return $updated; 587 } 588 589 /** 590 * clean_catalog_proc 591 * 592 * Removes songs that no longer exist. 593 */ 594 public function clean_catalog_proc() 595 { 596 $dead = 0; 597 $app = new DropboxApp($this->apikey, $this->secret, $this->authtoken); 598 $dropbox = new Dropbox($app); 599 600 $sql = 'SELECT `id`, `file` FROM `song` WHERE `catalog` = ?'; 601 $db_results = Dba::read($sql, array($this->id)); 602 while ($row = Dba::fetch_assoc($db_results)) { 603 debug_event('dropbox.catalog', 'Starting clean on ' . $row['file'] . '(' . $row['id'] . ')', 5, 604 'ampache-catalog'); 605 $file = $row['file']; 606 try { 607 $metadata = $dropbox->getMetadata($file, ["include_deleted" => true]); 608 } catch (DropboxClientException $e) { 609 if ($e->getCode() == 409) { 610 $dead++; 611 Dba::write('DELETE FROM `song` WHERE `id` = ?', array($row['id'])); 612 } else { 613 AmpError::add('general', T_('API Error: cannot connect to Dropbox.')); 614 } 615 } 616 } 617 $this->update_last_clean(); 618 619 return $dead; 620 } 621 622 /** 623 * move_catalog_proc 624 * This function updates the file path of the catalog to a new location (unsupported) 625 * @param string $new_path 626 * @return boolean 627 */ 628 public function move_catalog_proc($new_path) 629 { 630 return false; 631 } 632 633 /** 634 * @return boolean 635 */ 636 public function cache_catalog_proc() 637 { 638 return false; 639 } 640 641 /** 642 * check_remote_song 643 * 644 * checks to see if a remote song exists in the database or not 645 * if it find a song it returns the UID 646 * @param $file 647 * @return boolean|mixed 648 */ 649 public function check_remote_file($file) 650 { 651 $is_audio_file = Catalog::is_audio_file($file); 652 if ($is_audio_file) { 653 $sql = 'SELECT `id` FROM `song` WHERE `file` = ?'; 654 } else { 655 $sql = 'SELECT `id` FROM `video` WHERE `file` = ?'; 656 } 657 $db_results = Dba::read($sql, array($file)); 658 if ($results = Dba::fetch_assoc($db_results)) { 659 return $results['id']; 660 } 661 662 return false; 663 } 664 665 /** 666 * @param $file 667 * @return string 668 */ 669 public function get_virtual_path($file) 670 { 671 return $this->apikey . '|' . $file; 672 } 673 674 /** 675 * @param string $file_path 676 * @return false|string 677 */ 678 public function get_rel_path($file_path) 679 { 680 $path = strpos($file_path, "|"); 681 if ($path !== false) { 682 $path++; 683 } 684 685 return substr($file_path, $path); 686 } 687 688 /** 689 * format 690 * 691 * This makes the object human-readable. 692 */ 693 public function format() 694 { 695 parent::format(); 696 $this->f_info = $this->apikey; 697 $this->f_full_info = $this->apikey; 698 } 699 700 /** 701 * @param Podcast_Episode|Song|Song_Preview|Video $media 702 * @return Media|Podcast_Episode|Song|Song_Preview|Video|null 703 */ 704 public function prepare_media($media) 705 { 706 $app = new DropboxApp($this->apikey, $this->secret, $this->authtoken); 707 $dropbox = new Dropbox($app); 708 try { 709 set_time_limit(0); 710 $meta = $dropbox->getMetadata($media->file); 711 712 $outfile = sys_get_temp_dir() . "/" . $meta->getName(); 713 714 // Download File 715 $this->download($dropbox, $media->file, null, $outfile); 716 $media->file = $outfile; 717 // Generate browser class for sending headers 718 fclose($outfile); 719 } catch (DropboxClientException $e) { 720 debug_event('dropbox.catalog', 'File not found on Dropbox: ' . $media->file, 5); 721 } 722 723 return $media; 724 } 725 726 /** 727 * gather_art 728 * 729 * This runs through all of the albums and finds art for them 730 * This runs through all of the needs art albums and tries 731 * to find the art for them from the mp3s 732 * @param integer[]|null $songs 733 * @param integer[]|null $videos 734 * @return boolean 735 * @throws DropboxClientException 736 */ 737 public function gather_art($songs = null, $videos = null) 738 { 739 740 // Make sure they've actually got methods 741 $art_order = AmpConfig::get('art_order'); 742 if (!count($art_order)) { 743 debug_event('dropbox.catalog', 'art_order not set, Catalog::gather_art aborting', 3); 744 745 return true; 746 } 747 $app = new DropboxApp($this->apikey, $this->secret, $this->authtoken); 748 $dropbox = new Dropbox($app); 749 $songs = $this->get_songs(); 750 751 // Prevent the script from timing out 752 set_time_limit(0); 753 754 $search_count = 0; 755 $searches = array(); 756 if ($songs == null) { 757 $searches['album'] = $this->get_album_ids(); 758 $searches['artist'] = $this->get_artist_ids(); 759 } else { 760 $searches['album'] = array(); 761 $searches['artist'] = array(); 762 foreach ($songs as $song) { 763 if ($song->id) { 764 $meta = $dropbox->getMetadata($song->file); 765 $outfile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $meta->getName(); 766 767 // Download File 768 $res = $this->download($dropbox, $song->file, 40960, $outfile); 769 if ($res) { 770 $sql = "UPDATE `song` SET `file` = ? WHERE `id` = ?"; 771 Dba::write($sql, array($outfile, $song->id)); 772 parent::gather_art([$song->id]); 773 $sql = "UPDATE `song` SET `file` = ? WHERE `id` = ?"; 774 Dba::write($sql, array($song->file, $song->id)); 775 $search_count++; 776 if (Ui::check_ticker()) { 777 Ui::update_text('count_art_' . $this->id, $search_count); 778 } 779 } 780 } 781 } 782 } 783 784 // One last time for good measure 785 Ui::update_text('count_art_' . $this->id, $search_count); 786 787 return true; 788 } 789 790 /** 791 * @deprecated Inject by constructor 792 */ 793 private function getUtilityFactory(): UtilityFactoryInterface 794 { 795 global $dic; 796 797 return $dic->get(UtilityFactoryInterface::class); 798 } 799} 800