introduced ResourcesDB class for easily managing all kind of resources in the future

git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@1856 0778d3d1-df1d-0410-868b-ea421aaaa00d
This commit is contained in:
Tobias Doerffel
2008-11-29 22:30:26 +00:00
parent 49a00ac211
commit bb186c5b62
2 changed files with 993 additions and 0 deletions

407
include/resources_db.h Normal file
View File

@@ -0,0 +1,407 @@
/*
* resources_db.h - header file for ResourcesDB
*
* Copyright (c) 2008 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef _RESOURCES_DB_H
#define _RESOURCES_DB_H
#include <QtCore/QDateTime>
#include <QtCore/QFileSystemWatcher>
#include <QtCore/QHash>
#include <QtCore/QStringList>
#include <QtXml/QDomDocument>
#define foreachTreeItem(list) \
for(TreeItemList::Iterator it=list.begin();it!=list.end();++it)
#define foreachConstTreeItem(list) \
for(TreeItemList::ConstIterator it=list.begin(); \
it!=list.end();++it)
class ResourcesDB : public QObject
{
Q_OBJECT
public:
class Item;
class TreeItem;
typedef QHash<QString, Item> ItemList;
typedef QList<TreeItem> TreeItemList;
class Item
{
public:
enum BaseDirectories
{
BaseRoot,
BaseWorkingDir,
BaseDataDir,
BaseHome,
NumBaseDirectories
} ;
enum Types
{
TypeUnknown,
TypeDirectory,
TypeSample,
TypeSoundFont,
TypePreset,
TypeProject,
TypeForeignProject,
TypePlugin,
TypeImage,
NumTypes
} ;
Item( const QString & _name,
Types _type,
BaseDirectories _base_dir = BaseWorkingDir,
const QString & _path = QString::null,
const QString & _hash = QString::null,
const QString & _tags = QString::null,
int _size = -1,
const QDateTime & _last_mod = QDateTime() ) :
m_name( _name ),
m_type( _type ),
m_baseDir( _base_dir ),
m_path( _path ),
m_hash( _hash ),
m_size( _size ),
m_lastMod( _last_mod ),
m_tags( _tags ),
m_treeItem( NULL )
{
init();
}
Item() :
m_name(),
m_type( TypeUnknown ),
m_baseDir( BaseRoot ),
m_path(),
m_hash(),
m_size( -1 ),
m_lastMod(),
m_tags(),
m_treeItem( NULL )
{
init();
}
const QString & name( void ) const
{
return m_name;
}
Types type( void ) const
{
return m_type;
}
const QString & path( void ) const
{
return m_path;
}
BaseDirectories baseDir( void ) const
{
return m_baseDir;
}
QString fullPath( void ) const
{
return getBaseDirectory( m_baseDir ) + m_path;
}
QString fullName( void ) const
{
if( m_type == TypeDirectory )
{
return fullPath();
}
return fullPath()+name();
}
const QString & hash( void ) const
{
return m_hash;
}
int size( void ) const
{
return m_size;
}
bool isShippedResource( void ) const
{
return baseDir() == BaseDataDir;
}
const QString & tags( void ) const
{
return m_tags;
}
bool isValid( void ) const
{
return !m_name.isEmpty() && m_type != TypeUnknown;
}
void setTreeItem( TreeItem * _ti )
{
m_treeItem = _ti;
}
TreeItem * treeItem( void )
{
return m_treeItem;
}
const QDateTime & lastMod( void ) const
{
return m_lastMod;
}
void setLastMod( const QDateTime & _date )
{
m_lastMod = _date;
}
void reload( void );
bool operator==( const Item & _other ) const;
// rates equality with given item
int equalityLevel( const Item & _other ) const;
Types guessType( void ) const;
static QString getBaseDirectory( BaseDirectories _bd );
private:
void init( void );
QString m_name;
Types m_type;
BaseDirectories m_baseDir;
QString m_path;
QString m_hash;
int m_size;
QDateTime m_lastMod;
QString m_tags;
TreeItem * m_treeItem;
} ;
class TreeItem
{
public:
TreeItem( TreeItem * _parent = NULL, Item * _item = NULL ) :
m_parent( _parent ),
m_hidden( false ),
m_temporaryMarker( false ),
m_item( _item )
{
}
inline void setHidden( bool _h )
{
m_hidden = _h;
}
inline bool isHidden( void ) const
{
return m_hidden;
}
inline int rowCount( void ) const
{
int rc = 0;
foreachConstTreeItem( m_children )
{
if( !it->isHidden() )
{
++rc;
}
}
return rc;
}
TreeItem * getChild( int _row )
{
int rc = 0;
foreachTreeItem( m_children )
{
if( !it->isHidden() )
{
if( rc == _row )
{
return &( *it );
}
++rc;
}
}
return NULL;
}
int row( void ) const
{
if( !m_parent )
{
return 0;
}
int row = 0;
foreachConstTreeItem( m_parent->m_children )
{
if( !it->isHidden() )
{
if( &( *it ) == this )
{
return row;
}
++row;
}
}
return 0;
}
inline void addChild( const TreeItem & _it )
{
m_children.push_back( _it );
}
inline TreeItemList & children( void )
{
return m_children;
}
inline const TreeItemList & children( void ) const
{
return m_children;
}
TreeItem * findChild( const QString & _name,
Item::BaseDirectories _base_dir )
{
foreachTreeItem( m_children )
{
if( it->item() && it->item()->name() == _name &&
it->item()->baseDir() == _base_dir )
{
return &( *it );
}
}
return NULL;
}
inline Item * item( void )
{
return m_item;
}
inline const Item * item( void ) const
{
return m_item;
}
inline TreeItem * parent( void )
{
return m_parent;
}
inline bool temporaryMarker( void ) const
{
return m_temporaryMarker;
}
inline void setTemporaryMarker( bool _on )
{
m_temporaryMarker = _on;
}
private:
TreeItem * m_parent;
QList<TreeItem> m_children;
bool m_hidden;
bool m_temporaryMarker;
Item * m_item;
} ;
ResourcesDB( const QString & _db_file );
~ResourcesDB();
void scanResources( void );
void load( void );
void save( void );
inline const ItemList & items( void ) const
{
return m_items;
}
inline TreeItem * topLevelNode( void )
{
return &m_topLevelNode;
}
const Item & nearestMatch( const Item & _item );
private slots:
void reloadDirectory( const QString & _path );
private:
void readDir( const QString & _dir, TreeItem * _parent,
Item::BaseDirectories _base_dir );
void recursiveRemoveItems( TreeItemList::Iterator _it );
void saveTreeItem( const TreeItem & _i, QDomDocument & _doc,
QDomElement & _de );
void loadTreeItem( TreeItem & _i, QDomElement & _de );
typedef QList<QPair<Item::BaseDirectories, QString> > FolderList;
FolderList m_folders;
QStringList m_scannedFolders;
QFileSystemWatcher m_watcher;
QString m_dbFile;
ItemList m_items;
TreeItem m_topLevelNode;
signals:
void itemsChanged( void );
} ;
#endif

586
src/core/resources_db.cpp Normal file
View File

@@ -0,0 +1,586 @@
/*
* resources_db.cpp - implementation of ResourcesDB
*
* Copyright (c) 2008 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <QtCore/QCryptographicHash>
#include <QtCore/QDir>
#include "resources_db.h"
#include "config_mgr.h"
#include "lmms_basics.h"
#include "mmp.h"
void ResourcesDB::Item::reload( void )
{
m_hash.clear();
m_size = -1;
init();
}
bool ResourcesDB::Item::operator==( const Item & _other ) const
{
return m_name == _other.m_name &&
m_type == _other.m_type &&
m_path == _other.m_path &&
m_hash == _other.m_hash &&
m_size == _other.m_size &&
m_tags == _other.m_tags;
}
int ResourcesDB::Item::equalityLevel( const Item & _other ) const
{
int l = 0;
if( m_path == _other.m_path && m_name == _other.m_name )
{
l += 40;
}
else if( m_name == _other.m_name )
{
l += 30;
}
else if( m_path == _other.m_path )
{
l += 10;
}
if( m_type == _other.m_type )
{
l += 5;
}
if( !m_tags.isEmpty() && !_other.m_tags.isEmpty() )
{
QStringList my_tags = m_tags.split( " " );
QStringList o_tags = _other.m_tags.split( " " );
foreach( const QString & tag, o_tags )
{
if( my_tags.contains( tag ) )
{
l += 10;
}
}
}
if( m_size == _other.m_size )
{
l += 20;
}
if( m_hash == _other.m_hash )
{
l += 100;
}
return l;
}
ResourcesDB::Item::Types ResourcesDB::Item::guessType( void ) const
{
static QMap<QString, Types> typeMap;
if( typeMap.isEmpty() )
{
typeMap["wav"] = TypeSample;
typeMap["ogg"] = TypeSample;
typeMap["mp3"] = TypeSample;
typeMap["ds"] = TypeSample;
typeMap["flac"] = TypeSample;
typeMap["spx"] = TypeSample;
typeMap["voc"] = TypeSample;
typeMap["au"] = TypeSample;
typeMap["raw"] = TypeSample;
typeMap["aif"] = TypeSample;
typeMap["aiff"] = TypeSample;
typeMap["sf2"] = TypeSoundFont;
typeMap["xpf"] = TypePreset;
typeMap["mmp"] = TypeProject;
typeMap["mmpz"] = TypeProject;
typeMap["mid"] = TypeForeignProject;
typeMap["flp"] = TypeForeignProject;
typeMap["dll"] = TypePlugin;
typeMap["so"] = TypePlugin;
typeMap["png"] = TypeImage;
typeMap["jpg"] = TypeImage;
typeMap["jpeg"] = TypeImage;
}
const QString s = QFileInfo( fullName() ).suffix().toLower();
QMap<QString, Types>::ConstIterator it = typeMap.find( s );
if( it != typeMap.end() )
{
return it.value();
}
return TypeUnknown;
}
void ResourcesDB::Item::init( void )
{
if( name().isEmpty() )
{
return;
}
// ensure trailing slash for path property
if( !m_path.isEmpty() && m_path.right( 1 ) != QDir::separator() )
{
m_path += QDir::separator();
}
if( m_type == TypeUnknown )
{
m_type = guessType();
}
// if item is a directory, ensure a trailing slash
if( m_type == TypeDirectory )
{
if( !m_name.isEmpty() &&
m_name.right( 1 ) != QDir::separator() )
{
m_name += QDir::separator();
}
if( m_hash.isEmpty() )
{
QCryptographicHash h( QCryptographicHash::Sha1 );
h.addData( fullName().toUtf8() );
m_hash = h.result().toHex();
}
}
// only stat file if we really need to
else if( ( m_hash.isEmpty() || m_size < 0 ) &&
QFile::exists( fullName() ) )
{
if( m_size < 0 )
{
m_size = QFileInfo( fullName() ).size();
}
if( m_hash.isEmpty() )
{
printf("rehash %s\n", fullName().toAscii().constData());
QCryptographicHash h( QCryptographicHash::Sha1 );
QFile f( fullName() );
f.open( QFile::ReadOnly );
const int chunkSize = 1024*1024; // 1 MB
for( int i = 0; i < f.size() / chunkSize; ++i )
{
h.addData( f.read( chunkSize ) );
}
h.addData( f.readAll() );
m_hash = h.result().toHex();
}
}
}
QString ResourcesDB::Item::getBaseDirectory( BaseDirectories _bd )
{
QString d;
switch( _bd )
{
case BaseRoot:
d = QDir::rootPath();
break;
case BaseWorkingDir:
d = configManager::inst()->workingDir();
break;
case BaseDataDir:
d = configManager::inst()->dataDir();
break;
case BaseHome:
default:
d = QDir::homePath();
break;
}
if( !d.isEmpty() && d.right( 1 ) != QDir::separator() )
{
d += QDir::separator();
}
return d;
}
ResourcesDB::ResourcesDB( const QString & _db_file ) :
m_watcher( this ),
m_dbFile( _db_file )
{
m_folders += qMakePair( Item::BaseDataDir, QString() );
m_folders += qMakePair( Item::BaseWorkingDir, QString() );
if( QFile::exists( m_dbFile ) )
{
load();
}
// (re-) scan directories
scanResources();
save();
connect( &m_watcher, SIGNAL( directoryChanged( const QString & ) ),
this, SLOT( reloadDirectory( const QString & ) ) );
}
ResourcesDB::~ResourcesDB()
{
save();
}
void ResourcesDB::load( void )
{
m_items.clear();
multimediaProject m( m_dbFile );
loadTreeItem( m_topLevelNode, m.content() );
}
void ResourcesDB::save( void )
{
multimediaProject m( multimediaProject::ResourcesDatabase );
saveTreeItem( m_topLevelNode, m, m.content() );
m.writeFile( m_dbFile );
}
void ResourcesDB::saveTreeItem( const TreeItem & _i, QDomDocument & _doc,
QDomElement & _de )
{
QDomElement e = _i.item() ? _doc.createElement( "item" ) : _de;
foreachConstTreeItem( _i.children() )
{
saveTreeItem( *it, _doc, e );
}
if( _i.item() )
{
const Item * it = _i.item();
e.setAttribute( "name", it->name() );
e.setAttribute( "type", it->type() );
e.setAttribute( "basedir", it->baseDir() );
e.setAttribute( "path", it->path() );
e.setAttribute( "hash", it->hash() );
e.setAttribute( "size", it->size() );
e.setAttribute( "tags", it->tags() );
e.setAttribute( "lastmod", it->lastMod().
toString( Qt::ISODate ) );
_de.appendChild( e );
}
}
void ResourcesDB::loadTreeItem( TreeItem & _i, QDomElement & _de )
{
QDomNode node = _de.firstChild();
while( !node.isNull() )
{
if( node.isElement() )
{
QDomElement e = node.toElement();
const QString h = e.attribute( "hash" );
if( !h.isEmpty() )
{
m_items[h] = Item( e.attribute( "name" ),
static_cast<Item::Types>( e.attribute( "type" ).toInt() ),
static_cast<Item::BaseDirectories>( e.attribute( "basedir" ).toInt() ),
e.attribute( "path" ),
h,
e.attribute( "tags" ),
e.attribute( "size" ).toInt(),
QDateTime::fromString( e.attribute( "lastmod" ), Qt::ISODate ) );
_i.addChild( TreeItem( &_i, &m_items[h] ) );
m_items[h].setTreeItem( &_i.children().last() );
if( m_items[h].type() == Item::TypeDirectory &&
QFileInfo( m_items[h].fullPath() ).isDir() )
{
m_watcher.addPath(
m_items[h].fullPath() );
}
loadTreeItem( _i.children().last(), e );
}
}
node = node.nextSibling();
}
}
void ResourcesDB::scanResources( void )
{
for( FolderList::ConstIterator it = m_folders.begin();
it != m_folders.end(); ++it )
{
readDir( it->second, &m_topLevelNode, it->first );
}
}
const ResourcesDB::Item & ResourcesDB::nearestMatch( const Item & _item )
{
if( !_item.hash().isEmpty() )
{
ItemList::ConstIterator it = m_items.find( _item.hash() );
if( it != m_items.end() )
{
return it.value();
}
}
int max_level = -1;
const Item * max_item = NULL;
foreach( const Item & it, m_items )
{
const int l = it.equalityLevel( _item );
if( l > max_level )
{
max_item = &it;
}
}
Q_ASSERT( max_item != NULL );
return *max_item;
}
void ResourcesDB::reloadDirectory( const QString & _path )
{
TreeItem * dirTreeItem = NULL;
foreach( Item it, m_items )
{
if( it.type() == Item::TypeDirectory && it.fullPath() == _path )
{
dirTreeItem = it.treeItem();
}
}
if( dirTreeItem )
{
Item * dirItem = dirTreeItem->item();
if( dirItem )
{
m_scannedFolders.clear();
readDir( dirItem->path(), dirTreeItem->parent(),
dirItem->baseDir() );
}
}
emit itemsChanged();
}
void ResourcesDB::recursiveRemoveItems( TreeItemList::Iterator _it )
{
for( TreeItemList::Iterator ch_it = _it->children().begin();
ch_it != _it->children().end(); ++ch_it )
{
recursiveRemoveItems( ch_it );
}
if( _it->item() )
{
if( _it->item()->type() == Item::TypeDirectory )
{
m_watcher.removePath( _it->item()->fullPath() );
}
m_items.remove( _it->item()->hash() );
}
}
void ResourcesDB::readDir( const QString & _dir, TreeItem * _parent,
Item::BaseDirectories _base_dir )
{
#ifdef LMMS_BUILD_LINUX
if( _dir.startsWith( "/dev" ) ||
_dir.startsWith( "/sys" ) ||
_dir.startsWith( "/proc" ) )
{
return;
}
#endif
QDir d( Item::getBaseDirectory( _base_dir ) + _dir );
m_scannedFolders << d.canonicalPath();
Item * parentItem;
TreeItem * curParent = _parent->findChild( d.dirName() +
QDir::separator(),
_base_dir );
printf("read dir: %s (%d)\n", d.canonicalPath().toAscii().constData(), curParent );
if( curParent )
{
parentItem = curParent->item();
foreachTreeItem( curParent->children() )
{
it->setTemporaryMarker( false );
}
}
else
{
// create new item for current dir
Item parent( d.dirName(), Item::TypeDirectory,
_base_dir, _parent->item() ?
_parent->item()->path() + d.dirName() +
QDir::separator() :
QString::null );
parent.setLastMod( QFileInfo(
d.canonicalPath() ).lastModified() );
parentItem = &( m_items[parent.hash()] = parent );
_parent->addChild( TreeItem( _parent, parentItem ) );
curParent = &_parent->children().last();
curParent->setTemporaryMarker( true );
parentItem->setTreeItem( curParent );
m_watcher.addPath( parent.fullPath() );
}
QFileInfoList list = d.entryInfoList( QDir::NoDotAndDotDot |
QDir::Dirs | QDir::Files |
QDir::Readable,
QDir::Name | QDir::DirsFirst );
foreach( QFileInfo f, list )
{
if( f.isSymLink() )
{
f = QFileInfo( f.symLinkTarget() );
}
QString fname = f.fileName();
if( f.isDir() )
{
fname += QDir::separator();
}
TreeItem * curChild = curParent->findChild( fname, _base_dir );
if( curChild )
{
curChild->setTemporaryMarker( true );
if( f.lastModified() > curChild->item()->lastMod() )
{
printf("reload: %s\n", fname.toAscii().constData());
curChild->item()->setLastMod(
f.lastModified() );
if( curChild->item()->type() ==
Item::TypeDirectory )
{
readDir( _dir + fname, curParent,
_base_dir );
}
else
{
curChild->item()->reload();
}
}
}
else
{
if( f.isDir() &&
!m_scannedFolders.contains(
f.canonicalFilePath() ) )
{
readDir( _dir + fname, curParent, _base_dir );
}
else if( f.isFile() )
{
Item i( f.fileName(), Item::TypeUnknown,
_base_dir, _dir );
i.setLastMod( f.lastModified() );
TreeItem ti( curParent,
&( m_items[i.hash()] = i ) );
ti.setTemporaryMarker( true );
curParent->addChild( ti );
}
}
}
for( TreeItemList::Iterator it = curParent->children().begin();
it != curParent->children().end(); )
{
if( it->temporaryMarker() == false )
{
printf("removing %s\n", it->item()->name().toAscii().constData() );
recursiveRemoveItems( it );
it = curParent->children().erase( it );
}
else
{
++it;
}
}
}
#include "moc_resources_db.cxx"