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