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