xref: /haiku/src/apps/mediaplayer/playlist/Playlist.cpp (revision 746cac055adc6ac3308c7bc2d29040fb95689cc9)
1 /*
2  * Playlist.cpp - Media Player for the Haiku Operating System
3  *
4  * Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de>
5  * Copyright (C) 2007 Stephan Aßmus <superstippi@gmx.de>
6  * Copyright (C) 2008 Fredrik Modéen 	<fredrik@modeen.se> (I have no problem changing my things to MIT)
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * version 2 as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  *
21  */
22 #include "Playlist.h"
23 
24 #include <debugger.h>
25 #include <new.h>
26 #include <stdio.h>
27 
28 #include <Autolock.h>
29 #include <Directory.h>
30 #include <File.h>
31 #include <Message.h>
32 #include <Mime.h>
33 #include <NodeInfo.h>
34 #include <Path.h>
35 #include <String.h>
36 
37 #include "FileReadWrite.h"
38 
39 using std::nothrow;
40 
41 // TODO: using BList for objects is bad, replace it with a template
42 
43 Playlist::Listener::Listener() {}
44 Playlist::Listener::~Listener() {}
45 void Playlist::Listener::RefAdded(const entry_ref& ref, int32 index) {}
46 void Playlist::Listener::RefRemoved(int32 index) {}
47 void Playlist::Listener::RefsSorted() {}
48 void Playlist::Listener::CurrentRefChanged(int32 newIndex) {}
49 
50 
51 // #pragma mark -
52 
53 
54 Playlist::Playlist()
55  :	BLocker("playlist lock")
56  ,	fRefs()
57  ,	fCurrentIndex(-1)
58 {
59 }
60 
61 
62 Playlist::~Playlist()
63 {
64 	MakeEmpty();
65 
66 	if (fListeners.CountItems() > 0)
67 		debugger("Playlist::~Playlist() - there are still listeners attached!");
68 }
69 
70 
71 // #pragma mark - archiving
72 
73 
74 static const char* kPathKey = "path";
75 
76 
77 status_t
78 Playlist::UnArchive(const BMessage* archive)
79 {
80 	if (archive == NULL)
81 		return B_BAD_VALUE;
82 
83 	MakeEmpty();
84 
85 	BString path;
86 	for (int32 i = 0; archive->FindString(kPathKey, i, &path) == B_OK; i++) {
87 		BEntry entry(path.String(), false);
88 			// don't follow links, we want to do that when opening files only
89 		entry_ref ref;
90 		if (entry.GetRef(&ref) != B_OK)
91 			continue;
92 		if (!AddRef(ref))
93 			return B_NO_MEMORY;
94 	}
95 
96 	return B_OK;
97 }
98 
99 
100 status_t
101 Playlist::Archive(BMessage* into) const
102 {
103 	if (into == NULL)
104 		return B_BAD_VALUE;
105 
106 	int32 count = fRefs.CountItems();
107 	for (int32 i = 0; i < count; i++) {
108 		const entry_ref* ref = (entry_ref*)fRefs.ItemAtFast(i);
109 		BPath path(ref);
110 		if (path.InitCheck() != B_OK)
111 			continue;
112 		status_t ret = into->AddString(kPathKey, path.Path());
113 		if (ret != B_OK)
114 			return ret;
115 	}
116 
117 	return B_OK;
118 }
119 
120 
121 // #pragma mark - list access
122 
123 
124 void
125 Playlist::MakeEmpty()
126 {
127 	int32 count = fRefs.CountItems();
128 	for (int32 i = count - 1; i >= 0; i--) {
129 		entry_ref* ref = (entry_ref*)fRefs.RemoveItem(i);
130 		_NotifyRefRemoved(i);
131 		delete ref;
132 	}
133 	SetCurrentRefIndex(-1);
134 }
135 
136 
137 int32
138 Playlist::CountItems() const
139 {
140 	return fRefs.CountItems();
141 }
142 
143 
144 void
145 Playlist::Sort()
146 {
147 	fRefs.SortItems(playlist_cmp);
148 	_NotifyRefsSorted();
149 }
150 
151 
152 bool
153 Playlist::AddRef(const entry_ref &ref)
154 {
155 	return AddRef(ref, CountItems());
156 }
157 
158 
159 bool
160 Playlist::AddRef(const entry_ref &ref, int32 index)
161 {
162 	entry_ref* copy = new (nothrow) entry_ref(ref);
163 	if (!copy)
164 		return false;
165 	if (!fRefs.AddItem(copy, index)) {
166 		delete copy;
167 		return false;
168 	}
169 	_NotifyRefAdded(ref, index);
170 
171 	if (index <= fCurrentIndex)
172 		SetCurrentRefIndex(fCurrentIndex + 1);
173 
174 	return true;
175 }
176 
177 
178 bool
179 Playlist::AdoptPlaylist(Playlist& other)
180 {
181 	return AdoptPlaylist(other, CountItems());
182 }
183 
184 
185 bool
186 Playlist::AdoptPlaylist(Playlist& other, int32 index)
187 {
188 	if (&other == this)
189 		return false;
190 	// NOTE: this is not intended to merge two "equal" playlists
191 	// the given playlist is assumed to be a temporary "dummy"
192 	if (fRefs.AddList(&other.fRefs, index)) {
193 		// take care of the notifications
194 		int32 count = other.fRefs.CountItems();
195 		for (int32 i = index; i < index + count; i++) {
196 			entry_ref* ref = (entry_ref*)fRefs.ItemAtFast(i);
197 			_NotifyRefAdded(*ref, i);
198 		}
199 		if (index <= fCurrentIndex)
200 			SetCurrentRefIndex(fCurrentIndex + count);
201 		// empty the other list, so that the entry_refs are no ours
202 		other.fRefs.MakeEmpty();
203 		return true;
204 	}
205 	return false;
206 }
207 
208 
209 entry_ref
210 Playlist::RemoveRef(int32 index, bool careAboutCurrentIndex)
211 {
212 	entry_ref _ref;
213 	entry_ref* ref = (entry_ref*)fRefs.RemoveItem(index);
214 	if (!ref)
215 		return _ref;
216 	_NotifyRefRemoved(index);
217 	_ref = *ref;
218 	delete ref;
219 
220 	if (careAboutCurrentIndex) {
221 		if (index == fCurrentIndex)
222 			SetCurrentRefIndex(-1);
223 		else if (index < fCurrentIndex)
224 			SetCurrentRefIndex(fCurrentIndex - 1);
225 	}
226 
227 	return _ref;
228 }
229 
230 
231 int32
232 Playlist::IndexOf(const entry_ref& _ref) const
233 {
234 	int32 count = CountItems();
235 	for (int32 i = 0; i < count; i++) {
236 		entry_ref* ref = (entry_ref*)fRefs.ItemAtFast(i);
237 		if (*ref == _ref)
238 			return i;
239 	}
240 	return -1;
241 }
242 
243 
244 status_t
245 Playlist::GetRefAt(int32 index, entry_ref* _ref) const
246 {
247 	if (!_ref)
248 		return B_BAD_VALUE;
249 	entry_ref* ref = (entry_ref*)fRefs.ItemAt(index);
250 	if (!ref)
251 		return B_BAD_INDEX;
252 	*_ref = *ref;
253 
254 	return B_OK;
255 }
256 
257 
258 //bool
259 //Playlist::HasRef(const entry_ref& ref) const
260 //{
261 //	return IndexOf(ref) >= 0;
262 //}
263 
264 
265 
266 // #pragma mark - navigation
267 
268 
269 bool
270 Playlist::SetCurrentRefIndex(int32 index)
271 {
272 	bool result = true;
273 	if (index >= CountItems() || index < 0) {
274 		index = -1;
275 		result = false;
276 	}
277 
278 	if (index == fCurrentIndex)
279 		return result;
280 
281 	fCurrentIndex = index;
282 	_NotifyCurrentRefChanged(fCurrentIndex);
283 	return result;
284 }
285 
286 
287 int32
288 Playlist::CurrentRefIndex() const
289 {
290 	return fCurrentIndex;
291 }
292 
293 
294 void
295 Playlist::GetSkipInfo(bool* canSkipPrevious, bool* canSkipNext) const
296 {
297 	if (canSkipPrevious)
298 		*canSkipPrevious = fCurrentIndex > 0;
299 	if (canSkipNext)
300 		*canSkipNext = fCurrentIndex < CountItems() - 1;
301 }
302 
303 
304 // pragma mark -
305 
306 
307 bool
308 Playlist::AddListener(Listener* listener)
309 {
310 	BAutolock _(this);
311 	if (listener && !fListeners.HasItem(listener))
312 		return fListeners.AddItem(listener);
313 	return false;
314 }
315 
316 
317 void
318 Playlist::RemoveListener(Listener* listener)
319 {
320 	BAutolock _(this);
321 	fListeners.RemoveItem(listener);
322 }
323 
324 
325 // #pragma mark -
326 
327 
328 void
329 Playlist::AppendRefs(const BMessage* refsReceivedMessage, int32 appendIndex)
330 {
331 	// the playlist ist replaced by the refs in the message
332 	// or the refs are appended at the appendIndex
333 	// in the existing playlist
334 	bool add = appendIndex >= 0;
335 
336 	if (!add)
337 		MakeEmpty();
338 
339 	bool startPlaying = CountItems() == 0;
340 
341 	Playlist temporaryPlaylist;
342 	Playlist* playlist = add ? &temporaryPlaylist : this;
343 
344 	entry_ref ref;
345 	for (int i = 0; refsReceivedMessage->FindRef("refs", i, &ref) == B_OK; i++)
346 		AppendToPlaylistRecursive(ref, playlist);
347 	playlist->Sort();
348 
349 	if (add)
350 		AdoptPlaylist(temporaryPlaylist, appendIndex);
351 
352 	if (startPlaying) {
353 		// open first file
354 		SetCurrentRefIndex(0);
355 	}
356 }
357 
358 
359 /*static*/ void
360 Playlist::AppendToPlaylistRecursive(const entry_ref& ref, Playlist* playlist)
361 {
362 	// recursively append the ref (dive into folders)
363 	BEntry entry(&ref, true);
364 	if (entry.InitCheck() < B_OK) {
365 		printf("Not OK\n");
366 		return;
367 	}
368 
369 	if (!entry.Exists()) {
370 		BPath path = BPath(&ref);
371 		//printf("Don't exist - %s\n", path.Path());
372 		return;
373 	}
374 
375 	if (entry.IsDirectory()) {
376 		BDirectory dir(&entry);
377 		if (dir.InitCheck() < B_OK)
378 			return;
379 		entry.Unset();
380 		entry_ref subRef;
381 		while (dir.GetNextRef(&subRef) == B_OK)
382 			AppendToPlaylistRecursive(subRef, playlist);
383 	} else if (entry.IsFile()) {
384 		//printf("Is File\n");
385 		BString mimeString = _MIMEString(&ref);
386 		if (_IsMediaFile(mimeString)) {
387 			//printf("Adding\n");
388 			playlist->AddRef(ref);
389 		} else if (_IsPlaylist(mimeString)) {
390 			//printf("RunPlaylist thing\n");
391 			BFile file(&ref, B_READ_ONLY);
392 			FileReadWrite lineReader(&file);
393 
394 			BString str;
395 			entry_ref refPath;
396 			status_t err;
397 			BPath path;
398 			while (lineReader.Next(str)) {
399 				str = str.RemoveFirst("file://");
400 				str = str.RemoveLast("..");
401 				path = BPath(str.String());
402 				printf("Line %s\n", path.Path());
403 				if (path.Path() != NULL) {
404 					if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) {
405 						playlist->AddRef(refPath);
406 					} else
407 						printf("Error - %s: [%lx]\n", strerror(err), (int32) err);
408 				} else
409 					printf("Error - No File Found in playlist\n");
410 			}
411 		} else
412 			printf("MIME Type = %s\n", mimeString.String());
413 	}
414 }
415 
416 
417 // #pragma mark -
418 
419 
420 int
421 Playlist::playlist_cmp(const void *p1, const void *p2)
422 {
423 	// compare complete path
424 	BEntry a(*(const entry_ref **)p1, false);
425 	BEntry b(*(const entry_ref **)p2, false);
426 
427 	BPath aPath(&a);
428 	BPath bPath(&b);
429 
430 	return strcmp(aPath.Path(), bPath.Path());
431 }
432 
433 
434 /*static*/ void
435 Playlist::_AddPlayListFileToPlayList(const entry_ref& ref, Playlist* playlist)
436 {
437 
438 	BFile file(&ref, B_READ_ONLY);
439 	FileReadWrite lineReader(&file);
440 
441 	BString str;
442 	while (lineReader.Next(str)) {
443 		BPath path = BPath(str.String());
444 		entry_ref refPath;
445 		status_t err;
446 		if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) {
447 			AppendToPlaylistRecursive(refPath, playlist);
448 		} else
449 			printf("Error - %s: [%lx]\n", strerror(err), (int32) err);
450 	}
451 	/*
452 		Read line for x to the end of file (while?)
453 		make a enty_ref and call AppendToPlaylistRecursive(with new entry, playlist)
454 	*/
455 }
456 
457 
458 /*static*/ bool
459 Playlist::_IsMediaFile(const BString& mimeString)
460 {
461 	BMimeType superType;
462 	BMimeType fileType(mimeString.String());
463 
464 	if (fileType.GetSupertype(&superType) != B_OK)
465 		return false;
466 
467 	// TODO: some media files have other super types, I think
468 	// for example ASF has "application" super type... so it would
469 	// need special handling
470 	return (superType == "audio" || superType == "video");
471 }
472 
473 
474 /*static*/ bool
475 Playlist::_IsPlaylist(const BString& mimeString)
476 {
477 	return mimeString.Compare("text/x-playlist") == 0;
478 }
479 
480 
481 /*static*/ BString
482 Playlist::_MIMEString(const entry_ref* ref)
483 {
484 	BFile file(ref, B_READ_ONLY);
485 	BNodeInfo nodeInfo(&file);
486 	char mimeString[B_MIME_TYPE_LENGTH];
487 	if (nodeInfo.GetType(mimeString) != B_OK) {
488 		BMimeType type;
489 		if (BMimeType::GuessMimeType(ref, &type) != B_OK)
490 			return BString();
491 
492 		strlcpy(mimeString, type.Type(), B_MIME_TYPE_LENGTH);
493 		nodeInfo.SetType(type.Type());
494 	}
495 	return BString(mimeString);
496 }
497 
498 
499 void
500 Playlist::_NotifyRefAdded(const entry_ref& ref, int32 index) const
501 {
502 	BList listeners(fListeners);
503 	int32 count = listeners.CountItems();
504 	for (int32 i = 0; i < count; i++) {
505 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
506 		listener->RefAdded(ref, index);
507 	}
508 }
509 
510 
511 void
512 Playlist::_NotifyRefRemoved(int32 index) const
513 {
514 	BList listeners(fListeners);
515 	int32 count = listeners.CountItems();
516 	for (int32 i = 0; i < count; i++) {
517 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
518 		listener->RefRemoved(index);
519 	}
520 }
521 
522 
523 void
524 Playlist::_NotifyRefsSorted() const
525 {
526 	BList listeners(fListeners);
527 	int32 count = listeners.CountItems();
528 	for (int32 i = 0; i < count; i++) {
529 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
530 		listener->RefsSorted();
531 	}
532 }
533 
534 
535 void
536 Playlist::_NotifyCurrentRefChanged(int32 newIndex) const
537 {
538 	BList listeners(fListeners);
539 	int32 count = listeners.CountItems();
540 	for (int32 i = 0; i < count; i++) {
541 		Listener* listener = (Listener*)listeners.ItemAtFast(i);
542 		listener->CurrentRefChanged(newIndex);
543 	}
544 }
545 
546