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 string = fRefs[0].name; 154 return B_OK; 155 } 156 157 return B_NOT_SUPPORTED; 158 } 159 160 161 status_t 162 FilePlaylistItem::SetAttribute(const Attribute& attribute, 163 const int32& value) 164 { 165 switch (attribute) { 166 case ATTR_INT32_TRACK: 167 return _SetAttribute("Audio:Track", B_INT32_TYPE, &value, 168 sizeof(int32)); 169 case ATTR_INT32_YEAR: 170 return _SetAttribute("Media:Year", B_INT32_TYPE, &value, 171 sizeof(int32)); 172 case ATTR_INT32_RATING: 173 return _SetAttribute("Media:Rating", B_INT32_TYPE, &value, 174 sizeof(int32)); 175 176 default: 177 return B_NOT_SUPPORTED; 178 } 179 } 180 181 182 status_t 183 FilePlaylistItem::GetAttribute(const Attribute& attribute, 184 int32& value) const 185 { 186 switch (attribute) { 187 case ATTR_INT32_TRACK: 188 return _GetAttribute("Audio:Track", B_INT32_TYPE, &value, 189 sizeof(int32)); 190 case ATTR_INT32_YEAR: 191 return _GetAttribute("Media:Year", B_INT32_TYPE, &value, 192 sizeof(int32)); 193 case ATTR_INT32_RATING: 194 return _GetAttribute("Media:Rating", B_INT32_TYPE, &value, 195 sizeof(int32)); 196 197 default: 198 return B_NOT_SUPPORTED; 199 } 200 } 201 202 203 status_t 204 FilePlaylistItem::SetAttribute(const Attribute& attribute, 205 const int64& value) 206 { 207 switch (attribute) { 208 case ATTR_INT64_FRAME: 209 return _SetAttribute("Media:Frame", B_INT64_TYPE, &value, 210 sizeof(int64)); 211 case ATTR_INT64_DURATION: 212 return _SetAttribute("Media:Length", B_INT64_TYPE, &value, 213 sizeof(int64)); 214 default: 215 return B_NOT_SUPPORTED; 216 } 217 } 218 219 220 status_t 221 FilePlaylistItem::GetAttribute(const Attribute& attribute, 222 int64& value) const 223 { 224 switch (attribute) { 225 case ATTR_INT64_FRAME: 226 return _GetAttribute("Media:Frame", B_INT64_TYPE, &value, 227 sizeof(int64)); 228 case ATTR_INT64_DURATION: 229 return _GetAttribute("Media:Length", B_INT64_TYPE, &value, 230 sizeof(int64)); 231 default: 232 return B_NOT_SUPPORTED; 233 } 234 } 235 236 237 status_t 238 FilePlaylistItem::SetAttribute(const Attribute& attribute, 239 const float& value) 240 { 241 if (attribute == ATTR_FLOAT_VOLUME) { 242 return _SetAttribute("Media:Volume", B_FLOAT_TYPE, &value, 243 sizeof(float)); 244 } 245 246 return B_NOT_SUPPORTED; 247 } 248 249 250 status_t 251 FilePlaylistItem::GetAttribute(const Attribute& attribute, 252 float& value) const 253 { 254 if (attribute == ATTR_FLOAT_VOLUME) { 255 return _GetAttribute("Media:Volume", B_FLOAT_TYPE, &value, 256 sizeof(float)); 257 } 258 259 return B_NOT_SUPPORTED; 260 } 261 262 263 // #pragma mark - 264 265 266 BString 267 FilePlaylistItem::LocationURI() const 268 { 269 BPath path(&fRefs[0]); 270 BString locationURI("file://"); 271 locationURI << path.Path(); 272 return locationURI; 273 } 274 275 276 status_t 277 FilePlaylistItem::GetIcon(BBitmap* bitmap, icon_size iconSize) const 278 { 279 BNode node(&fRefs[0]); 280 BNodeInfo info(&node); 281 return info.GetTrackerIcon(bitmap, iconSize); 282 } 283 284 285 status_t 286 FilePlaylistItem::MoveIntoTrash() 287 { 288 if (fNamesInTrash[0].Length() != 0) { 289 // Already in the trash! 290 return B_ERROR; 291 } 292 293 status_t err; 294 err = _MoveIntoTrash(&fRefs, &fNamesInTrash); 295 if (err != B_OK) 296 return err; 297 298 if (fImageRefs.empty()) 299 return B_OK; 300 301 err = _MoveIntoTrash(&fImageRefs, &fImageNamesInTrash); 302 if (err != B_OK) 303 return err; 304 305 return B_OK; 306 } 307 308 309 status_t 310 FilePlaylistItem::RestoreFromTrash() 311 { 312 if (fNamesInTrash[0].Length() <= 0) { 313 // Not in the trash! 314 return B_ERROR; 315 } 316 317 status_t err; 318 err = _RestoreFromTrash(&fRefs, &fNamesInTrash); 319 if (err != B_OK) 320 return err; 321 322 if (fImageRefs.empty()) 323 return B_OK; 324 325 err = _RestoreFromTrash(&fImageRefs, &fImageNamesInTrash); 326 if (err != B_OK) 327 return err; 328 329 return B_OK; 330 } 331 332 333 // #pragma mark - 334 335 TrackSupplier* 336 FilePlaylistItem::_CreateTrackSupplier() const 337 { 338 MediaFileTrackSupplier* supplier 339 = new(std::nothrow) MediaFileTrackSupplier(); 340 if (supplier == NULL) 341 return NULL; 342 343 for (vector<entry_ref>::size_type i = 0; i < fRefs.size(); i++) { 344 BMediaFile* mediaFile = new(std::nothrow) BMediaFile(&fRefs[i]); 345 if (mediaFile == NULL) { 346 delete supplier; 347 return NULL; 348 } 349 if (supplier->AddMediaFile(mediaFile) != B_OK) 350 delete mediaFile; 351 } 352 353 for (vector<entry_ref>::size_type i = 0; i < fImageRefs.size(); i++) { 354 BBitmap* bitmap = BTranslationUtils::GetBitmap(&fImageRefs[i]); 355 if (bitmap == NULL) 356 continue; 357 if (supplier->AddBitmap(bitmap) != B_OK) 358 delete bitmap; 359 } 360 361 // Search for subtitle files in the same folder 362 // TODO: Error checking 363 BEntry entry(&fRefs[0], true); 364 365 char originalName[B_FILE_NAME_LENGTH]; 366 entry.GetName(originalName); 367 BString nameWithoutExtension(originalName); 368 int32 extension = nameWithoutExtension.FindLast('.'); 369 if (extension > 0) 370 nameWithoutExtension.Truncate(extension); 371 372 BPath path; 373 entry.GetPath(&path); 374 path.GetParent(&path); 375 BDirectory directory(path.Path()); 376 while (directory.GetNextEntry(&entry) == B_OK) { 377 char name[B_FILE_NAME_LENGTH]; 378 if (entry.GetName(name) != B_OK) 379 continue; 380 BString nameString(name); 381 if (nameString == originalName) 382 continue; 383 if (nameString.IFindFirst(nameWithoutExtension) < 0) 384 continue; 385 386 BFile file(&entry, B_READ_ONLY); 387 if (file.InitCheck() != B_OK) 388 continue; 389 390 int32 pos = nameString.FindLast('.'); 391 if (pos < 0) 392 continue; 393 394 BString extensionString(nameString.String() + pos + 1); 395 extensionString.ToLower(); 396 397 BString language = "default"; 398 if (pos > 1) { 399 int32 end = pos; 400 while (pos > 0 && *(nameString.String() + pos - 1) != '.') 401 pos--; 402 language.SetTo(nameString.String() + pos, end - pos); 403 } 404 405 if (extensionString == "srt") { 406 SubTitles* subTitles 407 = new(std::nothrow) SubTitlesSRT(&file, language.String()); 408 if (subTitles != NULL && !supplier->AddSubTitles(subTitles)) 409 delete subTitles; 410 } 411 } 412 413 return supplier; 414 } 415 416 417 status_t 418 FilePlaylistItem::AddRef(const entry_ref& ref) 419 { 420 fRefs.push_back(ref); 421 fNamesInTrash.push_back(""); 422 return B_OK; 423 } 424 425 426 status_t 427 FilePlaylistItem::AddImageRef(const entry_ref& ref) 428 { 429 fImageRefs.push_back(ref); 430 fImageNamesInTrash.push_back(""); 431 return B_OK; 432 } 433 434 435 const entry_ref& 436 FilePlaylistItem::ImageRef() const 437 { 438 static entry_ref ref; 439 440 if (fImageRefs.empty()) 441 return ref; 442 443 return fImageRefs[0]; 444 } 445 446 447 bigtime_t 448 FilePlaylistItem::_CalculateDuration() 449 { 450 BMediaFile mediaFile(&Ref()); 451 452 if (mediaFile.InitCheck() != B_OK || mediaFile.CountTracks() < 1) 453 return 0; 454 455 return mediaFile.TrackAt(0)->Duration(); 456 } 457 458 459 status_t 460 FilePlaylistItem::_SetAttribute(const char* attrName, type_code type, 461 const void* data, size_t size) 462 { 463 BEntry entry(&fRefs[0], true); 464 BNode node(&entry); 465 if (node.InitCheck() != B_OK) 466 return node.InitCheck(); 467 468 ssize_t written = node.WriteAttr(attrName, type, 0, data, size); 469 if (written != (ssize_t)size) { 470 if (written < 0) 471 return (status_t)written; 472 return B_IO_ERROR; 473 } 474 return B_OK; 475 } 476 477 478 status_t 479 FilePlaylistItem::_GetAttribute(const char* attrName, type_code type, 480 void* data, size_t size) const 481 { 482 BEntry entry(&fRefs[0], true); 483 BNode node(&entry); 484 if (node.InitCheck() != B_OK) 485 return node.InitCheck(); 486 487 ssize_t read = node.ReadAttr(attrName, type, 0, data, size); 488 if (read != (ssize_t)size) { 489 if (read < 0) 490 return (status_t)read; 491 return B_IO_ERROR; 492 } 493 return B_OK; 494 } 495 496 497 status_t 498 FilePlaylistItem::_MoveIntoTrash(vector<entry_ref>* refs, 499 vector<BString>* namesInTrash) 500 { 501 char trashPath[B_PATH_NAME_LENGTH]; 502 status_t err = find_directory(B_TRASH_DIRECTORY, (*refs)[0].device, 503 true /*create it*/, trashPath, B_PATH_NAME_LENGTH); 504 if (err != B_OK) { 505 fprintf(stderr, "failed to find Trash: %s\n", strerror(err)); 506 return err; 507 } 508 509 BDirectory trashDir(trashPath); 510 err = trashDir.InitCheck(); 511 if (err != B_OK) { 512 fprintf(stderr, "failed to init BDirectory for %s: %s\n", 513 trashPath, strerror(err)); 514 return err; 515 } 516 517 for (vector<entry_ref>::size_type i = 0; i < refs->size(); i++) { 518 BEntry entry(&(*refs)[i]); 519 err = entry.InitCheck(); 520 if (err != B_OK) { 521 fprintf(stderr, "failed to init BEntry for %s: %s\n", 522 (*refs)[i].name, strerror(err)); 523 return err; 524 } 525 526 // Find a unique name for the entry in the trash 527 (*namesInTrash)[i] = (*refs)[i].name; 528 int32 uniqueNameIndex = 1; 529 while (true) { 530 BEntry test(&trashDir, (*namesInTrash)[i].String()); 531 if (!test.Exists()) 532 break; 533 (*namesInTrash)[i] = (*refs)[i].name; 534 (*namesInTrash)[i] << ' ' << uniqueNameIndex; 535 uniqueNameIndex++; 536 } 537 538 // Remember the original path 539 BPath originalPath; 540 entry.GetPath(&originalPath); 541 542 // Finally, move the entry into the trash 543 err = entry.MoveTo(&trashDir, (*namesInTrash)[i].String()); 544 if (err != B_OK) { 545 fprintf(stderr, "failed to move entry into trash %s: %s\n", 546 trashPath, strerror(err)); 547 return err; 548 } 549 550 // Allow Tracker to restore this entry 551 BNode node(&entry); 552 BString originalPathString(originalPath.Path()); 553 node.WriteAttrString("_trk/original_path", &originalPathString); 554 } 555 556 return B_OK; 557 } 558 559 560 status_t 561 FilePlaylistItem::_RestoreFromTrash(vector<entry_ref>* refs, 562 vector<BString>* namesInTrash) 563 { 564 char trashPath[B_PATH_NAME_LENGTH]; 565 status_t err = find_directory(B_TRASH_DIRECTORY, (*refs)[0].device, 566 false /*create it*/, trashPath, B_PATH_NAME_LENGTH); 567 if (err != B_OK) { 568 fprintf(stderr, "failed to find Trash: %s\n", strerror(err)); 569 return err; 570 } 571 572 for (vector<entry_ref>::size_type i = 0; i < refs->size(); i++) { 573 // construct the entry to the file in the trash 574 // TODO: BEntry(const BDirectory* directory, const char* path) is broken! 575 // BEntry entry(trashPath, (*namesInTrash)[i].String()); 576 BPath path(trashPath, (*namesInTrash)[i].String()); 577 BEntry entry(path.Path()); 578 err = entry.InitCheck(); 579 if (err != B_OK) { 580 fprintf(stderr, "failed to init BEntry for %s: %s\n", 581 (*namesInTrash)[i].String(), strerror(err)); 582 return err; 583 } 584 //entry.GetPath(&path); 585 //printf("moving '%s'\n", path.Path()); 586 587 // construct the folder of the original entry_ref 588 node_ref nodeRef; 589 nodeRef.device = (*refs)[i].device; 590 nodeRef.node = (*refs)[i].directory; 591 BDirectory originalDir(&nodeRef); 592 err = originalDir.InitCheck(); 593 if (err != B_OK) { 594 fprintf(stderr, "failed to init original BDirectory for " 595 "%s: %s\n", (*refs)[i].name, strerror(err)); 596 return err; 597 } 598 599 //path.SetTo(&originalDir, fItems[i].name); 600 //printf("as '%s'\n", path.Path()); 601 602 // Reset the name here, the user may have already moved the entry 603 // out of the trash via Tracker for example. 604 (*namesInTrash)[i] = ""; 605 606 // Finally, move the entry back into the original folder 607 err = entry.MoveTo(&originalDir, (*refs)[i].name); 608 if (err != B_OK) { 609 fprintf(stderr, "failed to restore entry from trash " 610 "%s: %s\n", (*refs)[i].name, strerror(err)); 611 return err; 612 } 613 614 // Remove the attribute that helps Tracker restore the entry. 615 BNode node(&entry); 616 node.RemoveAttr("_trk/original_path"); 617 } 618 619 return B_OK; 620 } 621 622