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