* Implemented attribute handling - you can change them, but they are not stored yet.

* Implemented CDDB ID computation (that's what BeOS's cdda-fs is using).
* Some other minor changes.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21144 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler
2007-05-15 17:33:05 +00:00
parent 85d733da06
commit e8113cabe0
7 changed files with 646 additions and 33 deletions
@@ -7,4 +7,5 @@ UsePrivateHeaders [ FDirName storage ] ;
KernelAddon cdda :
kernel_interface.cpp
cdda.cpp
cddb.cpp
;
+14 -2
View File
@@ -56,7 +56,7 @@ is_garbage(char c)
static void
sanitize_string(char *string)
sanitize_string(char *&string)
{
if (string == NULL)
return;
@@ -78,6 +78,12 @@ sanitize_string(char *string)
while (length > 1 && isspace(string[length - 1])) {
string[--length] = '\0';
}
if (!string[0]) {
// free string if it's empty
free(string);
string = NULL;
}
}
@@ -127,7 +133,13 @@ sanitize_album(cdtext &text)
cut_string(text.album, text.artist);
sanitize_string(text.album);
if ((text.artist == NULL || !text.artist[0]) && text.album != NULL) {
if (text.album != NULL && !strcasecmp(text.album, "My CD")) {
// don't laugh, people really do that!
free(text.album);
text.album = NULL;
}
if ((text.artist == NULL || text.artist[0] == NULL) && text.album != NULL) {
// try to extract artist from album
char *space = strstr(text.album, " ");
if (space != NULL) {
@@ -0,0 +1,47 @@
/*
* Copyright 2007, Axel Dörfler, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "cddb.h"
static int32
cddb_sum(int32 n)
{
int32 result = 0;
while (n > 0) {
result = result + (n % 10);
n = n / 10;
}
return result;
}
// #pragma mark - exported functions
/*!
Computes the CD Disc ID as specified in the FreeDB how-to (code taken from there).
*/
uint32
compute_cddb_disc_id(scsi_toc_toc &toc)
{
int32 numTracks = toc.last_track + 1 - toc.first_track;
uint32 n = 0;
for (int32 i = 0; i < numTracks; i++) {
n = n + cddb_sum((toc.tracks[i].start.time.minute * 60)
+ toc.tracks[i].start.time.second);
}
int32 t = ((toc.tracks[numTracks].start.time.minute * 60)
+ toc.tracks[numTracks].start.time.second)
- ((toc.tracks[0].start.time.minute * 60)
+ toc.tracks[0].start.time.second);
return (n % 0xff) << 24 | t << 8 | numTracks;
}
@@ -0,0 +1,14 @@
/*
* Copyright 2007, Axel Dörfler, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef CDDB_H
#define CDDB_H
#include <bus/scsi/scsi_cmds.h>
uint32 compute_cddb_disc_id(scsi_toc_toc &toc);
#endif // CDDB_H
@@ -10,8 +10,11 @@
#include <fs_info.h>
#include <fs_interface.h>
#include <KernelExport.h>
#include <Mime.h>
#include <TypeConstants.h>
#include <util/kernel_cpp.h>
#include <util/DoublyLinkedList.h>
#include <dirent.h>
#include <errno.h>
@@ -29,10 +32,14 @@
#endif
class Volume;
class Attribute;
class Inode;
struct attr_cookie;
struct dir_cookie;
typedef DoublyLinkedList<Attribute> AttributeList;
typedef DoublyLinkedList<attr_cookie> AttrCookieList;
class Volume {
public:
Volume(mount_id id);
@@ -73,6 +80,29 @@ class Volume {
Inode *fFirstEntry;
};
class Attribute : public DoublyLinkedListLinkImpl<Attribute> {
public:
Attribute(const char *name, type_code type);
~Attribute();
status_t InitCheck() const { return fName != NULL ? B_OK : B_NO_MEMORY; }
status_t SetTo(const char *name, type_code type);
void SetType(type_code type) { fType = type; }
status_t ReadAt(off_t offset, uint8 *buffer, size_t *_length);
status_t WriteAt(off_t offset, const uint8 *buffer, size_t *_length);
void Truncate();
const char *Name() const { return fName; }
size_t Size() const { return fSize; }
type_code Type() const { return fType; }
private:
char *fName;
type_code fType;
uint8 *fData;
size_t fSize;
};
class Inode {
public:
@@ -103,6 +133,18 @@ class Inode {
off_t Size() const
{ return fFrameCount * kFrameSize /* + WAV header */; }
Attribute *FindAttribute(const char *name) const;
status_t AddAttribute(const char *name, type_code type,
const uint8 *data = 0, size_t length = 0);
status_t AddAttribute(const char *name, type_code type,
const char *string);
status_t AddAttribute(const char *name, int32 value);
status_t RemoveAttribute(const char *name);
void AddAttrCookie(attr_cookie *cookie);
void RemoveAttrCookie(attr_cookie *cookie);
void RewindAttrCookie(attr_cookie *cookie);
Inode *Next() const { return fNext; }
void SetNext(Inode *inode) { fNext = inode; }
@@ -117,12 +159,13 @@ class Inode {
time_t fModificationTime;
off_t fStartFrame;
off_t fFrameCount;
AttributeList fAttributes;
AttrCookieList fAttrCookies;
};
struct dir_cookie {
Inode *current;
int state; // iteration state
Inode *current;
int state; // iteration state
};
// directory iteration states
@@ -133,8 +176,12 @@ enum {
ITERATION_STATE_BEGIN = ITERATION_STATE_DOT,
};
struct attr_cookie : DoublyLinkedListLinkImpl<attr_cookie> {
Attribute *current;
};
struct file_cookie {
int open_mode;
int open_mode;
};
@@ -212,6 +259,7 @@ Volume::Mount(const char* device)
scsi_cd_msf& next = toc->tracks[i + 1].start.time;
// the last track is always lead-out
scsi_cd_msf& start = toc->tracks[i].start.time;
int32 track = i + 1;
off_t startFrame = start.minute * kFramesPerMinute
+ start.second * kFramesPerSecond + start.frame;
@@ -219,18 +267,43 @@ Volume::Mount(const char* device)
+ next.second * kFramesPerSecond + next.frame
- startFrame;
if (text.titles[i] != NULL && text.titles[i]) {
if (text.artists[i] != NULL && text.artists[i]) {
snprintf(title, sizeof(title), "%02ld. %s - %s.wav", i + 1,
const char *artist = text.artists[i] != NULL
? text.artists[i] : text.artist;
if (text.titles[i] != NULL) {
if (artist != NULL) {
snprintf(title, sizeof(title), "%02ld. %s - %s.wav", track,
text.artists[i], text.titles[i]);
} else {
snprintf(title, sizeof(title), "%02ld. %s.wav", i + 1,
snprintf(title, sizeof(title), "%02ld. %s.wav", track,
text.titles[i]);
}
} else
snprintf(title, sizeof(title), "%02ld.wav", i + 1);
snprintf(title, sizeof(title), "%02ld.wav", track);
_CreateNode(fRootNode, title, startFrame, frames, S_IFREG | 0444);
// remove '/' from title
for (int32 j = 0; title[j]; j++) {
if (title[j] == '/')
title[j] = '-';
}
Inode *inode = _CreateNode(fRootNode, title, startFrame, frames,
S_IFREG | 0444);
if (inode == NULL)
continue;
// add attributes
inode->AddAttribute("Audio:Artist", B_STRING_TYPE, artist);
inode->AddAttribute("Audio:Title", B_STRING_TYPE, text.titles[i]);
inode->AddAttribute("Audio:Genre", B_STRING_TYPE, text.genre);
inode->AddAttribute("Audio:Track", track);
snprintf(title, sizeof(title), "%02lu:%02lu",
uint32(inode->FrameCount() / kFramesPerMinute),
uint32((inode->FrameCount() % kFramesPerMinute) / kFramesPerSecond));
inode->AddAttribute("Audio:Length", B_STRING_TYPE, title);
inode->AddAttribute("BEOS:TYPE", B_MIME_STRING_TYPE, "audio/x-wav");
}
free(toc);
@@ -330,6 +403,116 @@ Volume::SetName(const char *name)
// #pragma mark -
Attribute::Attribute(const char *name, type_code type)
:
fName(NULL),
fType(0),
fData(NULL),
fSize(0)
{
SetTo(name, type);
}
Attribute::~Attribute()
{
free(fName);
free(fData);
}
status_t
Attribute::SetTo(const char *name, type_code type)
{
if (name == NULL || !name[0])
return B_BAD_VALUE;
name = strdup(name);
if (name == NULL)
return B_NO_MEMORY;
free(fName);
fName = (char *)name;
fType = type;
return B_OK;
}
status_t
Attribute::ReadAt(off_t offset, uint8 *buffer, size_t *_length)
{
size_t length = *_length;
if (offset < 0)
return B_BAD_VALUE;
if (offset >= length) {
*_length = 0;
return B_OK;
}
if (offset + length > fSize)
length = fSize - offset;
if (user_memcpy(buffer, fData + offset, length) < B_OK)
return B_BAD_ADDRESS;
*_length = length;
return B_OK;
}
status_t
Attribute::WriteAt(off_t offset, const uint8 *buffer, size_t *_length)
{
size_t length = *_length;
if (offset < 0)
return B_BAD_VALUE;
// we limit the attribute size to something reasonable
off_t end = offset + length;
if (end > 65536) {
end = 65536;
length = end - offset;
}
if (offset > end) {
*_length = 0;
return E2BIG;
}
if (end > fSize) {
// make room in the data stream
uint8 *data = (uint8 *)realloc(fData, end);
if (data == NULL)
return B_NO_MEMORY;
if (fSize < offset)
memset(data + fSize, 0, offset - fSize);
fData = data;
fSize = end;
}
if (user_memcpy(fData + offset, buffer, length) < B_OK)
return B_BAD_ADDRESS;
*_length = length;
return B_OK;
}
void
Attribute::Truncate()
{
free(fData);
fData = NULL;
fSize = 0;
}
// #pragma mark -
Inode::Inode(Volume *volume, Inode *parent, const char *name, off_t start,
off_t frames, int32 type)
:
@@ -383,6 +566,150 @@ Inode::SetName(const char* name)
}
Attribute *
Inode::FindAttribute(const char *name) const
{
if (name == NULL || !name[0])
return NULL;
AttributeList::ConstIterator iterator = fAttributes.GetIterator();
while (iterator.HasNext()) {
Attribute *attribute = iterator.Next();
if (!strcmp(attribute->Name(), name))
return attribute;
}
return NULL;
}
status_t
Inode::AddAttribute(const char *name, type_code type,
const uint8 *data, size_t length)
{
if (FindAttribute(name) != NULL)
return B_NAME_IN_USE;
Attribute *attribute = new Attribute(name, type);
status_t status = attribute != NULL ? B_OK : B_NO_MEMORY;
if (status == B_OK)
status = attribute->InitCheck();
if (status == B_OK && data != NULL && length != 0)
status = attribute->WriteAt(0, data, &length);
if (status < B_OK) {
delete attribute;
return status;
}
fAttributes.Add(attribute);
return B_OK;
}
status_t
Inode::AddAttribute(const char *name, type_code type,
const char *string)
{
if (string == NULL)
return NULL;
return AddAttribute(name, type, (const uint8 *)string,
strlen(string));
}
status_t
Inode::AddAttribute(const char *name, int32 value)
{
return AddAttribute(name, B_INT32_TYPE, (const uint8 *)&value,
sizeof(int32));
}
status_t
Inode::RemoveAttribute(const char *name)
{
if (name == NULL || !name[0])
return B_ENTRY_NOT_FOUND;
AttributeList::Iterator iterator = fAttributes.GetIterator();
while (iterator.HasNext()) {
Attribute *attribute = iterator.Next();
if (!strcmp(attribute->Name(), name)) {
// look for attribute in cookies
AttrCookieList::Iterator i = fAttrCookies.GetIterator();
while (i.HasNext()) {
attr_cookie *cookie = i.Next();
if (cookie->current == attribute)
cookie->current = attribute->GetDoublyLinkedListLink()->next;
}
iterator.Remove();
delete attribute;
return B_OK;
}
}
return B_ENTRY_NOT_FOUND;
}
void
Inode::AddAttrCookie(attr_cookie *cookie)
{
fAttrCookies.Add(cookie);
RewindAttrCookie(cookie);
}
void
Inode::RemoveAttrCookie(attr_cookie *cookie)
{
fAttrCookies.Remove(cookie);
}
void
Inode::RewindAttrCookie(attr_cookie *cookie)
{
cookie->current = fAttributes.First();
}
// #pragma mark -
void
fill_stat_buffer(Volume *volume, Inode *inode, Attribute *attribute,
struct stat &stat)
{
stat.st_dev = volume->ID();
stat.st_ino = inode->ID();
if (attribute != NULL) {
stat.st_size = attribute->Size();
stat.st_mode = S_ATTR | 0666;
stat.st_type = attribute->Type();
} else {
stat.st_size = inode->Size();
stat.st_mode = inode->Type();
stat.st_type = 0;
}
stat.st_nlink = 1;
stat.st_blksize = 2048;
stat.st_uid = inode->UserID();
stat.st_gid = inode->GroupID();
stat.st_atime = time(NULL);
stat.st_mtime = stat.st_ctime = inode->ModificationTime();
stat.st_crtime = inode->CreationTime();
}
// #pragma mark - module API
@@ -636,21 +963,7 @@ cdda_read_stat(fs_volume _volume, fs_vnode _node, struct stat *stat)
TRACE(("cdda_read_stat: vnode %p (0x%Lx), stat %p\n", inode, inode->ID(), stat));
stat->st_dev = volume->ID();
stat->st_ino = inode->ID();
stat->st_size = inode->Size();
stat->st_mode = inode->Type();
stat->st_nlink = 1;
stat->st_blksize = 2048;
stat->st_uid = inode->UserID();
stat->st_gid = inode->GroupID();
stat->st_atime = time(NULL);
stat->st_mtime = stat->st_ctime = inode->ModificationTime();
stat->st_crtime = inode->CreationTime();
fill_stat_buffer(volume, inode, NULL, *stat);
return B_OK;
}
@@ -813,6 +1126,228 @@ cdda_free_dir_cookie(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
}
// #pragma mark - attribute functions
static status_t
cdda_open_attr_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie *_cookie)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_vnode;
attr_cookie *cookie = new attr_cookie;
if (cookie == NULL)
return B_NO_MEMORY;
Locker _(volume->Lock());
inode->AddAttrCookie(cookie);
*_cookie = cookie;
return B_OK;
}
static status_t
cdda_close_attr_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
{
return B_OK;
}
static status_t
cdda_free_attr_dir_cookie(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_vnode;
attr_cookie *cookie = (attr_cookie *)_cookie;
Locker _(volume->Lock());
inode->RemoveAttrCookie(cookie);
delete cookie;
return B_OK;
}
static status_t
cdda_rewind_attr_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_vnode;
attr_cookie *cookie = (attr_cookie *)_cookie;
Locker _(volume->Lock());
inode->RewindAttrCookie(cookie);
return B_OK;
}
static status_t
cdda_read_attr_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie,
struct dirent *dirent, size_t bufferSize, uint32 *_num)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_vnode;
attr_cookie *cookie = (attr_cookie *)_cookie;
Locker _(volume->Lock());
Attribute *attribute = cookie->current;
if (attribute == NULL) {
*_num = 0;
return B_OK;
}
size_t length = strlcpy(dirent->d_name, attribute->Name(), bufferSize);
dirent->d_dev = volume->ID();
dirent->d_ino = inode->ID();
dirent->d_reclen = sizeof(struct dirent) + length;
cookie->current = attribute->GetDoublyLinkedListLink()->next;
*_num = 1;
return B_OK;
}
static status_t
cdda_create_attr(fs_volume _volume, fs_vnode _node, const char *name,
uint32 type, int openMode, fs_cookie *_cookie)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
Locker _(volume->Lock());
Attribute *attribute = inode->FindAttribute(name);
if (attribute == NULL) {
status_t status = inode->AddAttribute(name, type);
if (status < B_OK)
return status;
} else if ((openMode & O_EXCL) == 0) {
attribute->SetType(type);
} else
return B_FILE_EXISTS;
*_cookie = strdup(name);
if (*_cookie == NULL)
return B_NO_MEMORY;
return B_OK;
}
static status_t
cdda_open_attr(fs_volume _volume, fs_vnode _node, const char *name,
int openMode, fs_cookie *_cookie)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
Locker _(volume->Lock());
Attribute *attribute = inode->FindAttribute(name);
if (attribute == NULL)
return B_ENTRY_NOT_FOUND;
*_cookie = strdup(name);
if (*_cookie == NULL)
return B_NO_MEMORY;
return B_OK;
}
static status_t
cdda_close_attr(fs_volume _fs, fs_vnode _file, fs_cookie cookie)
{
return B_OK;
}
static status_t
cdda_free_attr_cookie(fs_volume _fs, fs_vnode _file, fs_cookie cookie)
{
free(cookie);
return B_OK;
}
static status_t
cdda_read_attr(fs_volume _volume, fs_vnode _node, fs_cookie _cookie,
off_t offset, void *buffer, size_t *_length)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
Locker _(volume->Lock());
Attribute *attribute = inode->FindAttribute((const char *)_cookie);
if (attribute == NULL)
return B_ENTRY_NOT_FOUND;
return attribute->ReadAt(offset, (uint8 *)buffer, _length);
}
static status_t
cdda_write_attr(fs_volume _volume, fs_vnode _file, fs_cookie _cookie,
off_t offset, const void *buffer, size_t *_length)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_file;
Locker _(volume->Lock());
Attribute *attribute = inode->FindAttribute((const char *)_cookie);
if (attribute == NULL)
return B_ENTRY_NOT_FOUND;
return attribute->WriteAt(offset, (uint8 *)buffer, _length);
}
static status_t
cdda_read_attr_stat(fs_volume _volume, fs_vnode _file, fs_cookie _cookie,
struct stat *stat)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_file;
Locker _(volume->Lock());
Attribute *attribute = inode->FindAttribute((const char *)_cookie);
if (attribute == NULL)
return B_ENTRY_NOT_FOUND;
fill_stat_buffer(volume, inode, attribute, *stat);
return B_OK;
}
static status_t
cdda_write_attr_stat(fs_volume _volume, fs_vnode file, fs_cookie cookie,
const struct stat *stat, int statMask)
{
return EOPNOTSUPP;
}
static status_t
cdda_remove_attr(fs_volume _volume, fs_vnode _node, const char *name)
{
if (name == NULL)
return B_BAD_VALUE;
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
Locker _(volume->Lock());
return inode->RemoveAttribute(name);
}
static status_t
cdda_std_ops(int32 op, ...)
{
@@ -873,7 +1408,7 @@ static file_system_module_info sCDDAFileSystem = {
NULL, // fs_symlink()
NULL, // fs_link()
NULL, // fs_unlink()
NULL, // fs_rename()
&cdda_rename,
NULL, // fs_access()
&cdda_read_stat,
@@ -896,7 +1431,6 @@ static file_system_module_info sCDDAFileSystem = {
&cdda_read_dir,
&cdda_rewind_dir,
#if 0
// attribute directory operations
&cdda_open_attr_dir,
&cdda_close_attr_dir,
@@ -914,9 +1448,8 @@ static file_system_module_info sCDDAFileSystem = {
&cdda_read_attr_stat,
&cdda_write_attr_stat,
&cdda_rename_attr,
NULL, // fs_rename_attr()
&cdda_remove_attr,
#endif
// the other operations are not yet supported (indices, queries)
NULL,
@@ -6,4 +6,5 @@ SimpleTest cdda_text
:
cdda_text.cpp
cdda.cpp
cddb.cpp
;
@@ -5,6 +5,7 @@
#include "cdda.h"
#include "cddb.h"
#include <fcntl.h>
#include <stdio.h>
@@ -36,7 +37,8 @@ main(int argc, char** argv)
return -1;
uint8 buffer[1024];
if (read_table_of_contents(fd, (scsi_toc_toc*)buffer, sizeof(buffer)) < 0) {
scsi_toc_toc *toc = (scsi_toc_toc *)buffer;
if (read_table_of_contents(fd, toc, sizeof(buffer)) < 0) {
fprintf(stderr, "%s: Retrieving TOC failed", __progname);
return -1;
}
@@ -44,5 +46,8 @@ main(int argc, char** argv)
cdtext text;
read_cdtext(fd, text);
uint32 id = compute_cddb_disc_id(*toc);
printf("CDDB disc ID: %lx\n", id);
close(fd);
}