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
22static const char* kPathKey = "path";
23
24FilePlaylistItem::FilePlaylistItem(const entry_ref& ref)
25{
26	fRefs.push_back(ref);
27	fNamesInTrash.push_back("");
28}
29
30
31FilePlaylistItem::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
41FilePlaylistItem::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
63FilePlaylistItem::~FilePlaylistItem()
64{
65}
66
67
68PlaylistItem*
69FilePlaylistItem::Clone() const
70{
71	return new (std::nothrow) FilePlaylistItem(*this);
72}
73
74
75BArchivable*
76FilePlaylistItem::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
88status_t
89FilePlaylistItem::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
107status_t
108FilePlaylistItem::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
147status_t
148FilePlaylistItem::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
160status_t
161FilePlaylistItem::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
181status_t
182FilePlaylistItem::GetAttribute(const Attribute& attribute,
183	int32& value) const
184{
185	return B_NOT_SUPPORTED;
186}
187
188
189status_t
190FilePlaylistItem::SetAttribute(const Attribute& attribute,
191	const int64& value)
192{
193	return B_NOT_SUPPORTED;
194}
195
196
197status_t
198FilePlaylistItem::GetAttribute(const Attribute& attribute,
199	int64& value) const
200{
201	return B_NOT_SUPPORTED;
202}
203
204
205// #pragma mark -
206
207
208BString
209FilePlaylistItem::LocationURI() const
210{
211	BPath path(&fRefs[0]);
212	BString locationURI("file://");
213	locationURI << path.Path();
214	return locationURI;
215}
216
217
218status_t
219FilePlaylistItem::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
227status_t
228FilePlaylistItem::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
251status_t
252FilePlaylistItem::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
277TrackSupplier*
278FilePlaylistItem::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
359status_t
360FilePlaylistItem::AddRef(const entry_ref& ref)
361{
362	fRefs.push_back(ref);
363	fNamesInTrash.push_back("");
364	return B_OK;
365}
366
367
368status_t
369FilePlaylistItem::AddImageRef(const entry_ref& ref)
370{
371	fImageRefs.push_back(ref);
372	fImageNamesInTrash.push_back("");
373	return B_OK;
374}
375
376
377const entry_ref&
378FilePlaylistItem::ImageRef() const
379{
380	static entry_ref ref;
381
382	if (fImageRefs.empty())
383		return ref;
384
385	return fImageRefs[0];
386}
387
388
389status_t
390FilePlaylistItem::_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
408status_t
409FilePlaylistItem::_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
427status_t
428FilePlaylistItem::_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
490status_t
491FilePlaylistItem::_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