⛏️ index : haiku.git

/*
 * Copyright 2009-2010 Stephan Aßmus <superstippi@gmx.de>
 * All rights reserved. Distributed under the terms of the MIT license.
 */

#include "FilePlaylistItem.h"

#include <stdio.h>

#include <new>

#include <Directory.h>
#include <File.h>
#include <FindDirectory.h>
#include <MediaFile.h>
#include <MediaTrack.h>
#include <Path.h>
#include <TranslationUtils.h>

#include "MediaFileTrackSupplier.h"
#include "SubTitlesSRT.h"

static const char* kPathKey = "path";

FilePlaylistItem::FilePlaylistItem(const entry_ref& ref)
{
	fRefs.push_back(ref);
	fNamesInTrash.push_back("");
}


FilePlaylistItem::FilePlaylistItem(const FilePlaylistItem& other)
	:
	fRefs(other.fRefs),
	fNamesInTrash(other.fNamesInTrash),
	fImageRefs(other.fImageRefs),
	fImageNamesInTrash(other.fImageNamesInTrash)
{
}


FilePlaylistItem::FilePlaylistItem(const BMessage* archive)
{
	const char* path;
	entry_ref	ref;
	if (archive != NULL) {
		int32 i = 0;
		while (archive->FindString(kPathKey, i, &path) == B_OK) {
			if (get_ref_for_path(path, &ref) == B_OK) {
				fRefs.push_back(ref);
			}
			i++;
		}
	}
	if (fRefs.empty()) {
		fRefs.push_back(entry_ref());
	}
	for (vector<entry_ref>::size_type i = 0; i < fRefs.size(); i++) {
		fNamesInTrash.push_back("");
	}
}


FilePlaylistItem::~FilePlaylistItem()
{
}


PlaylistItem*
FilePlaylistItem::Clone() const
{
	return new (std::nothrow) FilePlaylistItem(*this);
}


BArchivable*
FilePlaylistItem::Instantiate(BMessage* archive)
{
	if (validate_instantiation(archive, "FilePlaylistItem"))
		return new (std::nothrow) FilePlaylistItem(archive);

	return NULL;
}


// #pragma mark -


status_t
FilePlaylistItem::Archive(BMessage* into, bool deep) const
{
	status_t ret = BArchivable::Archive(into, deep);
	if (ret != B_OK)
		return ret;
	for (vector<entry_ref>::size_type i = 0; i < fRefs.size(); i++) {
		BPath path(&fRefs[i]);
		ret = path.InitCheck();
		if (ret != B_OK)
			return ret;
		ret = into->AddString(kPathKey, path.Path());
		if (ret != B_OK)
			return ret;
	}
	return B_OK;
}


status_t
FilePlaylistItem::SetAttribute(const Attribute& attribute,
	const BString& string)
{
	switch (attribute) {
		case ATTR_STRING_NAME:
		{
			BEntry entry(&fRefs[0], false);
			return entry.Rename(string.String(), false);
		}
	
		case ATTR_STRING_KEYWORDS:
			return _SetAttribute("Meta:Keywords", B_STRING_TYPE,
				string.String(), string.Length());

		case ATTR_STRING_ARTIST:
			return _SetAttribute("Audio:Artist", B_STRING_TYPE,
				string.String(), string.Length());
		case ATTR_STRING_AUTHOR:
			return _SetAttribute("Media:Author", B_STRING_TYPE,
				string.String(), string.Length());
		case ATTR_STRING_ALBUM:
			return _SetAttribute("Audio:Album", B_STRING_TYPE,
				string.String(), string.Length());
		case ATTR_STRING_TITLE:
			return _SetAttribute("Media:Title", B_STRING_TYPE,
				string.String(), string.Length());
		case ATTR_STRING_AUDIO_BITRATE:
			return _SetAttribute("Audio:Bitrate", B_STRING_TYPE,
				string.String(), string.Length());
		case ATTR_STRING_VIDEO_BITRATE:
			return _SetAttribute("Video:Bitrate", B_STRING_TYPE,
				string.String(), string.Length());

		default:
			return B_NOT_SUPPORTED;
	}
}


status_t
FilePlaylistItem::GetAttribute(const Attribute& attribute,
	BString& string) const
{
	if (attribute == ATTR_STRING_NAME) {
		if (fRefs[0].name == NULL)
			return B_NAME_NOT_FOUND;
		string = fRefs[0].name;
		return B_OK;
	}

	return B_NOT_SUPPORTED;
}


status_t
FilePlaylistItem::SetAttribute(const Attribute& attribute,
	const int32& value)
{
	switch (attribute) {
		case ATTR_INT32_TRACK:
			return _SetAttribute("Audio:Track", B_INT32_TYPE, &value,
				sizeof(int32));
		case ATTR_INT32_YEAR:
			return _SetAttribute("Media:Year", B_INT32_TYPE, &value,
				sizeof(int32));
		case ATTR_INT32_RATING:
			return _SetAttribute("Media:Rating", B_INT32_TYPE, &value,
				sizeof(int32));

		default:
			return B_NOT_SUPPORTED;
	}
}


status_t
FilePlaylistItem::GetAttribute(const Attribute& attribute,
	int32& value) const
{
	switch (attribute) {
		case ATTR_INT32_TRACK:
			return _GetAttribute("Audio:Track", B_INT32_TYPE, &value,
				sizeof(int32));
		case ATTR_INT32_YEAR:
			return _GetAttribute("Media:Year", B_INT32_TYPE, &value,
				sizeof(int32));
		case ATTR_INT32_RATING:
			return _GetAttribute("Media:Rating", B_INT32_TYPE, &value,
				sizeof(int32));

		default:
			return B_NOT_SUPPORTED;
	}
}


status_t
FilePlaylistItem::SetAttribute(const Attribute& attribute,
	const int64& value)
{
	switch (attribute) {
		case ATTR_INT64_FRAME:
			return _SetAttribute("Media:Frame", B_INT64_TYPE, &value,
				sizeof(int64));
		case ATTR_INT64_DURATION:
			return _SetAttribute("Media:Length", B_INT64_TYPE, &value,
				sizeof(int64));
		default:
			return B_NOT_SUPPORTED;
	}
}


status_t
FilePlaylistItem::GetAttribute(const Attribute& attribute,
	int64& value) const
{
	switch (attribute) {
		case ATTR_INT64_FRAME:
			return _GetAttribute("Media:Frame", B_INT64_TYPE, &value,
				sizeof(int64));
		case ATTR_INT64_DURATION:
			return _GetAttribute("Media:Length", B_INT64_TYPE, &value,
				sizeof(int64));
		default:
			return B_NOT_SUPPORTED;
	}
}


status_t
FilePlaylistItem::SetAttribute(const Attribute& attribute,
	const float& value)
{
	if (attribute == ATTR_FLOAT_VOLUME) {
		return _SetAttribute("Media:Volume", B_FLOAT_TYPE, &value,
			sizeof(float));
	}

	return B_NOT_SUPPORTED;
}


status_t
FilePlaylistItem::GetAttribute(const Attribute& attribute,
	float& value) const
{
	if (attribute == ATTR_FLOAT_VOLUME) {
		return _GetAttribute("Media:Volume", B_FLOAT_TYPE, &value,
			sizeof(float));
	}

	return B_NOT_SUPPORTED;
}


// #pragma mark -


BString
FilePlaylistItem::LocationURI() const
{
	BPath path(&fRefs[0]);
	BString locationURI("file://");
	locationURI << path.Path();
	return locationURI;
}


status_t
FilePlaylistItem::GetIcon(BBitmap* bitmap, icon_size iconSize) const
{
	BNode node(&fRefs[0]);
	BNodeInfo info(&node);
	return info.GetTrackerIcon(bitmap, iconSize);
}


status_t
FilePlaylistItem::MoveIntoTrash()
{
	if (fNamesInTrash[0].Length() != 0) {
		// Already in the trash!
		return B_ERROR;
	}

	status_t err;
	err = _MoveIntoTrash(&fRefs, &fNamesInTrash);
	if (err != B_OK)
		return err;
	
	if (fImageRefs.empty())
		return B_OK;

	err = _MoveIntoTrash(&fImageRefs, &fImageNamesInTrash);
	if (err != B_OK)
		return err;

	return B_OK;
}


status_t
FilePlaylistItem::RestoreFromTrash()
{
	if (fNamesInTrash[0].Length() <= 0) {
		// Not in the trash!
		return B_ERROR;
	}

	status_t err;
	err = _RestoreFromTrash(&fRefs, &fNamesInTrash);
	if (err != B_OK)
		return err;
	
	if (fImageRefs.empty())
		return B_OK;

	err = _RestoreFromTrash(&fImageRefs, &fImageNamesInTrash);
	if (err != B_OK)
		return err;

	return B_OK;
}


// #pragma mark -

TrackSupplier*
FilePlaylistItem::_CreateTrackSupplier() const
{
	MediaFileTrackSupplier* supplier
		= new(std::nothrow) MediaFileTrackSupplier();
	if (supplier == NULL)
		return NULL;

	for (vector<entry_ref>::size_type i = 0; i < fRefs.size(); i++) {
		BMediaFile* mediaFile = new(std::nothrow) BMediaFile(&fRefs[i]);
		if (mediaFile == NULL) {
			delete supplier;
			return NULL;
		}
		if (supplier->AddMediaFile(mediaFile) != B_OK)
			delete mediaFile;
	}

	for (vector<entry_ref>::size_type i = 0; i < fImageRefs.size(); i++) {
		BBitmap* bitmap = BTranslationUtils::GetBitmap(&fImageRefs[i]);
		if (bitmap == NULL)
			continue;
		if (supplier->AddBitmap(bitmap) != B_OK)
			delete bitmap;
	}

	// Search for subtitle files in the same folder
	// TODO: Error checking
	BEntry entry(&fRefs[0], true);

	char originalName[B_FILE_NAME_LENGTH];
	entry.GetName(originalName);
	BString nameWithoutExtension(originalName);
	int32 extension = nameWithoutExtension.FindLast('.');
	if (extension > 0)
		nameWithoutExtension.Truncate(extension);

	BPath path;
	entry.GetPath(&path);
	path.GetParent(&path);
	BDirectory directory(path.Path());
	while (directory.GetNextEntry(&entry) == B_OK) {
		char name[B_FILE_NAME_LENGTH];
		if (entry.GetName(name) != B_OK)
			continue;
		BString nameString(name);
		if (nameString == originalName)
			continue;
		if (nameString.IFindFirst(nameWithoutExtension) < 0)
			continue;

		BFile file(&entry, B_READ_ONLY);
		if (file.InitCheck() != B_OK)
			continue;

		int32 pos = nameString.FindLast('.');
		if (pos < 0)
			continue;

		BString extensionString(nameString.String() + pos + 1);
		extensionString.ToLower();

		BString language = "default";
		if (pos > 1) {
			int32 end = pos;
			while (pos > 0 && *(nameString.String() + pos - 1) != '.')
				pos--;
			language.SetTo(nameString.String() + pos, end - pos);
		}

		if (extensionString == "srt") {
			SubTitles* subTitles
				= new(std::nothrow) SubTitlesSRT(&file, language.String());
			if (subTitles != NULL && !supplier->AddSubTitles(subTitles))
				delete subTitles;
		}
	}

	return supplier;
}


status_t
FilePlaylistItem::AddRef(const entry_ref& ref)
{
	fRefs.push_back(ref);
	fNamesInTrash.push_back("");
	return B_OK;
}


status_t
FilePlaylistItem::AddImageRef(const entry_ref& ref)
{
	fImageRefs.push_back(ref);
	fImageNamesInTrash.push_back("");
	return B_OK;
}


const entry_ref&
FilePlaylistItem::ImageRef() const
{
	static entry_ref ref;

	if (fImageRefs.empty())
		return ref;
	
	return fImageRefs[0];
}


bigtime_t
FilePlaylistItem::_CalculateDuration()
{
	BMediaFile mediaFile(&Ref());

	if (mediaFile.InitCheck() != B_OK || mediaFile.CountTracks() < 1)
		return 0;

	return mediaFile.TrackAt(0)->Duration();
}


status_t
FilePlaylistItem::_SetAttribute(const char* attrName, type_code type,
	const void* data, size_t size)
{
	BEntry entry(&fRefs[0], true);
	BNode node(&entry);
	if (node.InitCheck() != B_OK)
		return node.InitCheck();

	ssize_t written = node.WriteAttr(attrName, type, 0, data, size);
	if (written != (ssize_t)size) {
		if (written < 0)
			return (status_t)written;
		return B_IO_ERROR;
	}
	return B_OK;
}


status_t
FilePlaylistItem::_GetAttribute(const char* attrName, type_code type,
	void* data, size_t size) const
{
	BEntry entry(&fRefs[0], true);
	BNode node(&entry);
	if (node.InitCheck() != B_OK)
		return node.InitCheck();

	ssize_t read = node.ReadAttr(attrName, type, 0, data, size);
	if (read != (ssize_t)size) {
		if (read < 0)
			return (status_t)read;
		return B_IO_ERROR;
	}
	return B_OK;
}


status_t
FilePlaylistItem::_MoveIntoTrash(vector<entry_ref>* refs,
	vector<BString>* namesInTrash)
{
	char trashPath[B_PATH_NAME_LENGTH];
	status_t err = find_directory(B_TRASH_DIRECTORY, (*refs)[0].device,
		true /*create it*/, trashPath, B_PATH_NAME_LENGTH);
	if (err != B_OK) {
		fprintf(stderr, "failed to find Trash: %s\n", strerror(err));
		return err;
	}

	BDirectory trashDir(trashPath);
	err = trashDir.InitCheck();
	if (err != B_OK) {
		fprintf(stderr, "failed to init BDirectory for %s: %s\n",
			trashPath, strerror(err));
		return err;
	}

	for (vector<entry_ref>::size_type i = 0; i < refs->size(); i++) {
		BEntry entry(&(*refs)[i]);
		err = entry.InitCheck();
		if (err != B_OK) {
			fprintf(stderr, "failed to init BEntry for %s: %s\n",
				(*refs)[i].name, strerror(err));
			return err;
		}
	
		// Find a unique name for the entry in the trash
		(*namesInTrash)[i] = (*refs)[i].name;
		int32 uniqueNameIndex = 1;
		while (true) {
			BEntry test(&trashDir, (*namesInTrash)[i].String());
			if (!test.Exists())
				break;
			(*namesInTrash)[i] = (*refs)[i].name;
			(*namesInTrash)[i] << ' ' << uniqueNameIndex;
			uniqueNameIndex++;
		}
	
		// Remember the original path
		BPath originalPath;
		entry.GetPath(&originalPath);
	
		// Finally, move the entry into the trash
		err = entry.MoveTo(&trashDir, (*namesInTrash)[i].String());
		if (err != B_OK) {
			fprintf(stderr, "failed to move entry into trash %s: %s\n",
				trashPath, strerror(err));
			return err;
		}
	
		// Allow Tracker to restore this entry
		BNode node(&entry);
		BString originalPathString(originalPath.Path());
		node.WriteAttrString("_trk/original_path", &originalPathString);
	}

	return B_OK;
}


status_t
FilePlaylistItem::_RestoreFromTrash(vector<entry_ref>* refs,
	vector<BString>* namesInTrash)
{
	char trashPath[B_PATH_NAME_LENGTH];
	status_t err = find_directory(B_TRASH_DIRECTORY, (*refs)[0].device,
		false /*create it*/, trashPath, B_PATH_NAME_LENGTH);
	if (err != B_OK) {
		fprintf(stderr, "failed to find Trash: %s\n", strerror(err));
		return err;
	}
	
	for (vector<entry_ref>::size_type i = 0; i < refs->size(); i++) {
		// construct the entry to the file in the trash
		// TODO: BEntry(const BDirectory* directory, const char* path) is broken!
		//	BEntry entry(trashPath, (*namesInTrash)[i].String());
		BPath path(trashPath, (*namesInTrash)[i].String());
		BEntry entry(path.Path());
		err = entry.InitCheck();
		if (err != B_OK) {
			fprintf(stderr, "failed to init BEntry for %s: %s\n",
				(*namesInTrash)[i].String(), strerror(err));
			return err;
		}
		//entry.GetPath(&path);
		//printf("moving '%s'\n", path.Path());
	
		// construct the folder of the original entry_ref
		node_ref nodeRef;
		nodeRef.device = (*refs)[i].device;
		nodeRef.node = (*refs)[i].directory;
		BDirectory originalDir(&nodeRef);
		err = originalDir.InitCheck();
		if (err != B_OK) {
			fprintf(stderr, "failed to init original BDirectory for "
				"%s: %s\n", (*refs)[i].name, strerror(err));
			return err;
		}
	
		//path.SetTo(&originalDir, fItems[i].name);
		//printf("as '%s'\n", path.Path());
	
		// Reset the name here, the user may have already moved the entry
		// out of the trash via Tracker for example.
		(*namesInTrash)[i] = "";
	
		// Finally, move the entry back into the original folder
		err = entry.MoveTo(&originalDir, (*refs)[i].name);
		if (err != B_OK) {
			fprintf(stderr, "failed to restore entry from trash "
				"%s: %s\n", (*refs)[i].name, strerror(err));
			return err;
		}
	
		// Remove the attribute that helps Tracker restore the entry.
		BNode node(&entry);
		node.RemoveAttr("_trk/original_path");
	}

	return B_OK;
}