From bb186c5b6258fe16b9307d77e71493f39e658e1c Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 29 Nov 2008 22:30:26 +0000 Subject: [PATCH] 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 --- include/resources_db.h | 407 ++++++++++++++++++++++++++ src/core/resources_db.cpp | 586 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 993 insertions(+) create mode 100644 include/resources_db.h create mode 100644 src/core/resources_db.cpp diff --git a/include/resources_db.h b/include/resources_db.h new file mode 100644 index 000000000..c91263ccb --- /dev/null +++ b/include/resources_db.h @@ -0,0 +1,407 @@ +/* + * resources_db.h - header file for ResourcesDB + * + * Copyright (c) 2008 Tobias Doerffel + * + * 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 +#include +#include +#include +#include + +#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 ItemList; + typedef QList 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 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 > FolderList; + FolderList m_folders; + QStringList m_scannedFolders; + QFileSystemWatcher m_watcher; + + QString m_dbFile; + + ItemList m_items; + TreeItem m_topLevelNode; + + +signals: + void itemsChanged( void ); + +} ; + + +#endif diff --git a/src/core/resources_db.cpp b/src/core/resources_db.cpp new file mode 100644 index 000000000..4a8dab186 --- /dev/null +++ b/src/core/resources_db.cpp @@ -0,0 +1,586 @@ +/* + * resources_db.cpp - implementation of ResourcesDB + * + * Copyright (c) 2008 Tobias Doerffel + * + * 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 +#include + +#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 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::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( e.attribute( "type" ).toInt() ), + static_cast( 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 = ⁢ + } + } + + 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"