1 /* 2 * Copyright 2009-2010 Stephan Aßmus <superstippi@gmx.de> 3 * All rights reserved. Distributed under the terms of the MIT license. 4 */ 5 6 #include "FilePlaylistItem.h" 7 8 #include <stdio.h> 9 10 #include <new> 11 12 #include <Directory.h> 13 #include <File.h> 14 #include <FindDirectory.h> 15 #include <MediaFile.h> 16 #include <MediaTrack.h> 17 #include <Path.h> 18 #include <TranslationUtils.h> 19 20 #include "MediaFileTrackSupplier.h" 21 #include "SubTitlesSRT.h" 22 23 static const char* kPathKey = "path"; 24 25 FilePlaylistItem::FilePlaylistItem(const entry_ref& ref) 26 { 27 fRefs.push_back(ref); 28 fNamesInTrash.push_back(""); 29 } 30 31 32 FilePlaylistItem::FilePlaylistItem(const FilePlaylistItem& other) 33 : 34 fRefs(other.fRefs), 35 fNamesInTrash(other.fNamesInTrash), 36 fImageRefs(other.fImageRefs), 37 fImageNamesInTrash(other.fImageNamesInTrash) 38 { 39 } 40 41 42 FilePlaylistItem::FilePlaylistItem(const BMessage* archive) 43 { 44 const char* path; 45 entry_ref ref; 46 if (archive != NULL) { 47 int32 i = 0; 48 while (archive->FindString(kPathKey, i, &path) == B_OK) { 49 if (get_ref_for_path(path, &ref) == B_OK) { 50 fRefs.push_back(ref); 51 } 52 i++; 53 } 54 } 55 if (fRefs.empty()) { 56 fRefs.push_back(entry_ref()); 57 } 58 for (vector<entry_ref>::size_type i = 0; i < fRefs.size(); i++) { 59 fNamesInTrash.push_back(""); 60 } 61 } 62 63 64 FilePlaylistItem::~FilePlaylistItem() 65 { 66 } 67 68 69 PlaylistItem* 70 FilePlaylistItem::Clone() const 71 { 72 return new (std::nothrow) FilePlaylistItem(*this); 73 } 74 75 76 BArchivable* 77 FilePlaylistItem::Instantiate(BMessage* archive) 78 { 79 if (validate_instantiation(archive, "FilePlaylistItem")) 80 return new (std::nothrow) FilePlaylistItem(archive); 81 82 return NULL; 83 } 84 85 86 // #pragma mark - 87 88 89 status_t 90 FilePlaylistItem::Archive(BMessage* into, bool deep) const 91 { 92 status_t ret = BArchivable::Archive(into, deep); 93 if (ret != B_OK) 94 return ret; 95 for (vector<entry_ref>::size_type i = 0; i < fRefs.size(); i++) { 96 BPath path(&fRefs[i]); 97 ret = path.InitCheck(); 98 if (ret != B_OK) 99 return ret; 100 ret = into->AddString(kPathKey, path.Path()); 101 if (ret != B_OK) 102 return ret; 103 } 104 return B_OK; 105 } 106 107 108 status_t 109 FilePlaylistItem::SetAttribute(const Attribute& attribute, 110 const BString& string) 111 { 112 switch (attribute) { 113 case ATTR_STRING_NAME: 114 { 115 BEntry entry(&fRefs[0], false); 116 return entry.Rename(string.String(), false); 117 } 118 119 case ATTR_STRING_KEYWORDS: 120 return _SetAttribute("Meta:Keywords", B_STRING_TYPE, 121 string.String(), string.Length()); 122 123 case ATTR_STRING_ARTIST: 124 return _SetAttribute("Audio:Artist", B_STRING_TYPE, 125 string.String(), string.Length()); 126 case ATTR_STRING_AUTHOR: 127 return _SetAttribute("Media:Author", B_STRING_TYPE, 128 string.String(), string.Length()); 129 case ATTR_STRING_ALBUM: 130 return _SetAttribute("Audio:Album", B_STRING_TYPE, 131 string.String(), string.Length()); 132 case ATTR_STRING_TITLE: 133 return _SetAttribute("Media:Title", B_STRING_TYPE, 134 string.String(), string.Length()); 135 case ATTR_STRING_AUDIO_BITRATE: 136 return _SetAttribute("Audio:Bitrate", B_STRING_TYPE, 137 string.String(), string.Length()); 138 case ATTR_STRING_VIDEO_BITRATE: 139 return _SetAttribute("Video:Bitrate", B_STRING_TYPE, 140 string.String(), string.Length()); 141 142 default: 143 return B_NOT_SUPPORTED; 144 } 145 } 146 147 148 status_t 149 FilePlaylistItem::GetAttribute(const Attribute& attribute, 150 BString& string) const 151 { 152 if (attribute == ATTR_STRING_NAME) { 153 if (fRefs[0].name == NULL) 154 return B_NAME_NOT_FOUND; 155 string = fRefs[0].name; 156 return B_OK; 157 } 158 159 return B_NOT_SUPPORTED; 160 } 161 162 163 status_t 164 FilePlaylistItem::SetAttribute(const Attribute& attribute, 165 const int32& value) 166 { 167 switch (attribute) { 168 case ATTR_INT32_TRACK: 169 return _SetAttribute("Audio:Track", B_INT32_TYPE, &value, 170 sizeof(int32)); 171 case ATTR_INT32_YEAR: 172 return _SetAttribute("Media:Year", B_INT32_TYPE, &value, 173 sizeof(int32)); 174 case ATTR_INT32_RATING: 175 return _SetAttribute("Media:Rating", B_INT32_TYPE, &value, 176 sizeof(int32)); 177 178 default: 179 return B_NOT_SUPPORTED; 180 } 181 } 182 183 184 status_t 185 FilePlaylistItem::GetAttribute(const Attribute& attribute, 186 int32& value) const 187 { 188 switch (attribute) { 189 case ATTR_INT32_TRACK: 190 return _GetAttribute("Audio:Track", B_INT32_TYPE, &value, 191 sizeof(int32)); 192 case ATTR_INT32_YEAR: 193 return _GetAttribute("Media:Year", B_INT32_TYPE, &value, 194 sizeof(int32)); 195 case ATTR_INT32_RATING: 196 return _GetAttribute("Media:Rating", B_INT32_TYPE, &value, 197 sizeof(int32)); 198 199 default: 200 return B_NOT_SUPPORTED; 201 } 202 } 203 204 205 status_t 206 FilePlaylistItem::SetAttribute(const Attribute& attribute, 207 const int64& value) 208 { 209 switch (attribute) { 210 case ATTR_INT64_FRAME: 211 return _SetAttribute("Media:Frame", B_INT64_TYPE, &value, 212 sizeof(int64)); 213 case ATTR_INT64_DURATION: 214 return _SetAttribute("Media:Length", B_INT64_TYPE, &value, 215 sizeof(int64)); 216 default: 217 return B_NOT_SUPPORTED; 218 } 219 } 220 221 222 status_t 223 FilePlaylistItem::GetAttribute(const Attribute& attribute, 224 int64& value) const 225 { 226 switch (attribute) { 227 case ATTR_INT64_FRAME: 228 return _GetAttribute("Media:Frame", B_INT64_TYPE, &value, 229 sizeof(int64)); 230 case ATTR_INT64_DURATION: 231 return _GetAttribute("Media:Length", B_INT64_TYPE, &value, 232 sizeof(int64)); 233 default: 234 return B_NOT_SUPPORTED; 235 } 236 } 237 238 239 status_t 240 FilePlaylistItem::SetAttribute(const Attribute& attribute, 241 const float& value) 242 { 243 if (attribute == ATTR_FLOAT_VOLUME) { 244 return _SetAttribute("Media:Volume", B_FLOAT_TYPE, &value, 245 sizeof(float)); 246 } 247 248 return B_NOT_SUPPORTED; 249 } 250 251 252 status_t 253 FilePlaylistItem::GetAttribute(const Attribute& attribute, 254 float& value) const 255 { 256 if (attribute == ATTR_FLOAT_VOLUME) { 257 return _GetAttribute("Media:Volume", B_FLOAT_TYPE, &value, 258 sizeof(float)); 259 } 260 261 return B_NOT_SUPPORTED; 262 } 263 264 265 // #pragma mark - 266 267 268 BString 269 FilePlaylistItem::LocationURI() const 270 { 271 BPath path(&fRefs[0]); 272 BString locationURI("file://"); 273 locationURI << path.Path(); 274 return locationURI; 275 } 276 277 278 status_t 279 FilePlaylistItem::GetIcon(BBitmap* bitmap, icon_size iconSize) const 280 { 281 BNode node(&fRefs[0]); 282 BNodeInfo info(&node); 283 return info.GetTrackerIcon(bitmap, iconSize); 284 } 285 286 287 status_t 288 FilePlaylistItem::MoveIntoTrash() 289 { 290 if (fNamesInTrash[0].Length() != 0) { 291 // Already in the trash! 292 return B_ERROR; 293 } 294 295 status_t err; 296 err = _MoveIntoTrash(&fRefs, &fNamesInTrash); 297 if (err != B_OK) 298 return err; 299 300 if (fImageRefs.empty()) 301 return B_OK; 302 303 err = _MoveIntoTrash(&fImageRefs, &fImageNamesInTrash); 304 if (err != B_OK) 305 return err; 306 307 return B_OK; 308 } 309 310 311 status_t 312 FilePlaylistItem::RestoreFromTrash() 313 { 314 if (fNamesInTrash[0].Length() <= 0) { 315 // Not in the trash! 316 return B_ERROR; 317 } 318 319 status_t err; 320 err = _RestoreFromTrash(&fRefs, &fNamesInTrash); 321 if (err != B_OK) 322 return err; 323 324 if (fImageRefs.empty()) 325 return B_OK; 326 327 err = _RestoreFromTrash(&fImageRefs, &fImageNamesInTrash); 328 if (err != B_OK) 329 return err; 330 331 return B_OK; 332 } 333 334 335 // #pragma mark - 336 337 TrackSupplier* 338 FilePlaylistItem::_CreateTrackSupplier() const 339 { 340 MediaFileTrackSupplier* supplier 341 = new(std::nothrow) MediaFileTrackSupplier(); 342 if (supplier == NULL) 343 return NULL; 344 345 for (vector<entry_ref>::size_type i = 0; i < fRefs.size(); i++) { 346 BMediaFile* mediaFile = new(std::nothrow) BMediaFile(&fRefs[i]); 347 if (mediaFile == NULL) { 348 delete supplier; 349 return NULL; 350 } 351 if (supplier->AddMediaFile(mediaFile) != B_OK) 352 delete mediaFile; 353 } 354 355 for (vector<entry_ref>::size_type i = 0; i < fImageRefs.size(); i++) { 356 BBitmap* bitmap = BTranslationUtils::GetBitmap(&fImageRefs[i]); 357 if (bitmap == NULL) 358 continue; 359 if (supplier->AddBitmap(bitmap) != B_OK) 360 delete bitmap; 361 } 362 363 // Search for subtitle files in the same folder 364 // TODO: Error checking 365 BEntry entry(&fRefs[0], true); 366 367 char originalName[B_FILE_NAME_LENGTH]; 368 entry.GetName(originalName); 369 BString nameWithoutExtension(originalName); 370 int32 extension = nameWithoutExtension.FindLast('.'); 371 if (extension > 0) 372 nameWithoutExtension.Truncate(extension); 373 374 BPath path; 375 entry.GetPath(&path); 376 path.GetParent(&path); 377 BDirectory directory(path.Path()); 378 while (directory.GetNextEntry(&entry) == B_OK) { 379 char name[B_FILE_NAME_LENGTH]; 380 if (entry.GetName(name) != B_OK) 381 continue; 382 BString nameString(name); 383 if (nameString == originalName) 384 continue; 385 if (nameString.IFindFirst(nameWithoutExtension) < 0) 386 continue; 387 388 BFile file(&entry, B_READ_ONLY); 389 if (file.InitCheck() != B_OK) 390 continue; 391 392 int32 pos = nameString.FindLast('.'); 393 if (pos < 0) 394 continue; 395 396 BString extensionString(nameString.String() + pos + 1); 397 extensionString.ToLower(); 398 399 BString language = "default"; 400 if (pos > 1) { 401 int32 end = pos; 402 while (pos > 0 && *(nameString.String() + pos - 1) != '.') 403 pos--; 404 language.SetTo(nameString.String() + pos, end - pos); 405 } 406 407 if (extensionString == "srt") { 408 SubTitles* subTitles 409 = new(std::nothrow) SubTitlesSRT(&file, language.String()); 410 if (subTitles != NULL && !supplier->AddSubTitles(subTitles)) 411 delete subTitles; 412 } 413 } 414 415 return supplier; 416 } 417 418 419 status_t 420 FilePlaylistItem::AddRef(const entry_ref& ref) 421 { 422 fRefs.push_back(ref); 423 fNamesInTrash.push_back(""); 424 return B_OK; 425 } 426 427 428 status_t 429 FilePlaylistItem::AddImageRef(const entry_ref& ref) 430 { 431 fImageRefs.push_back(ref); 432 fImageNamesInTrash.push_back(""); 433 return B_OK; 434 } 435 436 437 const entry_ref& 438 FilePlaylistItem::ImageRef() const 439 { 440 static entry_ref ref; 441 442 if (fImageRefs.empty()) 443 return ref; 444 445 return fImageRefs[0]; 446 } 447 448 449 bigtime_t 450 FilePlaylistItem::_CalculateDuration() 451 { 452 BMediaFile mediaFile(&Ref()); 453 454 if (mediaFile.InitCheck() != B_OK || mediaFile.CountTracks() < 1) 455 return 0; 456 457 return mediaFile.TrackAt(0)->Duration(); 458 } 459 460 461 status_t 462 FilePlaylistItem::_SetAttribute(const char* attrName, type_code type, 463 const void* data, size_t size) 464 { 465 BEntry entry(&fRefs[0], true); 466 BNode node(&entry); 467 if (node.InitCheck() != B_OK) 468 return node.InitCheck(); 469 470 ssize_t written = node.WriteAttr(attrName, type, 0, data, size); 471 if (written != (ssize_t)size) { 472 if (written < 0) 473 return (status_t)written; 474 return B_IO_ERROR; 475 } 476 return B_OK; 477 } 478 479 480 status_t 481 FilePlaylistItem::_GetAttribute(const char* attrName, type_code type, 482 void* data, size_t size) const 483 { 484 BEntry entry(&fRefs[0], true); 485 BNode node(&entry); 486 if (node.InitCheck() != B_OK) 487 return node.InitCheck(); 488 489 ssize_t read = node.ReadAttr(attrName, type, 0, data, size); 490 if (read != (ssize_t)size) { 491 if (read < 0) 492 return (status_t)read; 493 return B_IO_ERROR; 494 } 495 return B_OK; 496 } 497 498 499 status_t 500 FilePlaylistItem::_MoveIntoTrash(vector<entry_ref>* refs, 501 vector<BString>* namesInTrash) 502 { 503 char trashPath[B_PATH_NAME_LENGTH]; 504 status_t err = find_directory(B_TRASH_DIRECTORY, (*refs)[0].device, 505 true /*create it*/, trashPath, B_PATH_NAME_LENGTH); 506 if (err != B_OK) { 507 fprintf(stderr, "failed to find Trash: %s\n", strerror(err)); 508 return err; 509 } 510 511 BDirectory trashDir(trashPath); 512 err = trashDir.InitCheck(); 513 if (err != B_OK) { 514 fprintf(stderr, "failed to init BDirectory for %s: %s\n", 515 trashPath, strerror(err)); 516 return err; 517 } 518 519 for (vector<entry_ref>::size_type i = 0; i < refs->size(); i++) { 520 BEntry entry(&(*refs)[i]); 521 err = entry.InitCheck(); 522 if (err != B_OK) { 523 fprintf(stderr, "failed to init BEntry for %s: %s\n", 524 (*refs)[i].name, strerror(err)); 525 return err; 526 } 527 528 // Find a unique name for the entry in the trash 529 (*namesInTrash)[i] = (*refs)[i].name; 530 int32 uniqueNameIndex = 1; 531 while (true) { 532 BEntry test(&trashDir, (*namesInTrash)[i].String()); 533 if (!test.Exists()) 534 break; 535 (*namesInTrash)[i] = (*refs)[i].name; 536 (*namesInTrash)[i] << ' ' << uniqueNameIndex; 537 uniqueNameIndex++; 538 } 539 540 // Remember the original path 541 BPath originalPath; 542 entry.GetPath(&originalPath); 543 544 // Finally, move the entry into the trash 545 err = entry.MoveTo(&trashDir, (*namesInTrash)[i].String()); 546 if (err != B_OK) { 547 fprintf(stderr, "failed to move entry into trash %s: %s\n", 548 trashPath, strerror(err)); 549 return err; 550 } 551 552 // Allow Tracker to restore this entry 553 BNode node(&entry); 554 BString originalPathString(originalPath.Path()); 555 node.WriteAttrString("_trk/original_path", &originalPathString); 556 } 557 558 return B_OK; 559 } 560 561 562 status_t 563 FilePlaylistItem::_RestoreFromTrash(vector<entry_ref>* refs, 564 vector<BString>* namesInTrash) 565 { 566 char trashPath[B_PATH_NAME_LENGTH]; 567 status_t err = find_directory(B_TRASH_DIRECTORY, (*refs)[0].device, 568 false /*create it*/, trashPath, B_PATH_NAME_LENGTH); 569 if (err != B_OK) { 570 fprintf(stderr, "failed to find Trash: %s\n", strerror(err)); 571 return err; 572 } 573 574 for (vector<entry_ref>::size_type i = 0; i < refs->size(); i++) { 575 // construct the entry to the file in the trash 576 // TODO: BEntry(const BDirectory* directory, const char* path) is broken! 577 // BEntry entry(trashPath, (*namesInTrash)[i].String()); 578 BPath path(trashPath, (*namesInTrash)[i].String()); 579 BEntry entry(path.Path()); 580 err = entry.InitCheck(); 581 if (err != B_OK) { 582 fprintf(stderr, "failed to init BEntry for %s: %s\n", 583 (*namesInTrash)[i].String(), strerror(err)); 584 return err; 585 } 586 //entry.GetPath(&path); 587 //printf("moving '%s'\n", path.Path()); 588 589 // construct the folder of the original entry_ref 590 node_ref nodeRef; 591 nodeRef.device = (*refs)[i].device; 592 nodeRef.node = (*refs)[i].directory; 593 BDirectory originalDir(&nodeRef); 594 err = originalDir.InitCheck(); 595 if (err != B_OK) { 596 fprintf(stderr, "failed to init original BDirectory for " 597 "%s: %s\n", (*refs)[i].name, strerror(err)); 598 return err; 599 } 600 601 //path.SetTo(&originalDir, fItems[i].name); 602 //printf("as '%s'\n", path.Path()); 603 604 // Reset the name here, the user may have already moved the entry 605 // out of the trash via Tracker for example. 606 (*namesInTrash)[i] = ""; 607 608 // Finally, move the entry back into the original folder 609 err = entry.MoveTo(&originalDir, (*refs)[i].name); 610 if (err != B_OK) { 611 fprintf(stderr, "failed to restore entry from trash " 612 "%s: %s\n", (*refs)[i].name, strerror(err)); 613 return err; 614 } 615 616 // Remove the attribute that helps Tracker restore the entry. 617 BNode node(&entry); 618 node.RemoveAttr("_trk/original_path"); 619 } 620 621 return B_OK; 622 } 623 624