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