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