xref: /haiku/src/apps/mediaplayer/playlist/FilePlaylistItem.cpp (revision 42f51416eb68b0a79f50fe185b6d0ecb064c62dc)
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