27#include <core/config.h>
28#ifdef H2CORE_HAVE_LIBARCHIVE
30#include <archive_entry.h>
61 __info(
"No information available." ),
67 __components = std::make_shared<std::vector<std::shared_ptr<DrumkitComponent>>>();
82 __instruments = std::make_shared<InstrumentList>( other->get_instruments() );
84 __components = std::make_shared<std::vector<std::shared_ptr<DrumkitComponent>>>();
85 for (
const auto& pComponent : *other->get_components() ) {
86 __components->push_back( std::make_shared<DrumkitComponent>( pComponent ) );
96 bool* pLegacyFormatEncountered,
100 ERRORLOG( QString(
"[%1] is not valid drumkit folder" ).arg( sDrumkitPath ) );
107 doc.
read( sDrumkitFile, bSilent );
109 XMLNode root = doc.firstChildElement(
"drumkit_info" );
110 if ( root.isNull() ) {
111 ERRORLOG(
"drumkit_info node not found" );
115 bool bLegacyFormatEncountered =
false;
117 std::shared_ptr<Drumkit> pDrumkit =
nullptr;
119 const QString sDrumkitDir =
120 sDrumkitFile.left( sDrumkitFile.lastIndexOf(
"/" ) );
123 auto formatVersionNode = root.firstChildElement(
"formatVersion" );
124 if ( ! formatVersionNode.isNull() ) {
125 WARNINGLOG( QString(
"Drumkit [%1] was created with a more recent version of Hydrogen than the current one!" )
126 .arg( sDrumkitPath ) );
134 &bLegacyFormatEncountered, bSilent );
137 if ( pLegacyFormatEncountered !=
nullptr ) {
138 *pLegacyFormatEncountered = bLegacyFormatEncountered;
141 if ( pDrumkit ==
nullptr ) {
142 ERRORLOG( QString(
"Unable to load drumkit [%1]" ).arg( sDrumkitFile ) );
146 if ( bLegacyFormatEncountered && bUpgrade ) {
154 const QString& sDrumkitPath,
155 bool* pLegacyFormatEncountered,
158 QString sDrumkitName = node->
read_string(
"name",
"",
false,
false, bSilent );
159 if ( sDrumkitName.isEmpty() ) {
160 ERRORLOG(
"Drumkit has no name, abort" );
164 std::shared_ptr<Drumkit> pDrumkit = std::make_shared<Drumkit>();
166 pDrumkit->__path = sDrumkitPath;
167 pDrumkit->__name = sDrumkitName;
168 pDrumkit->__author = node->
read_string(
"author",
"undefined author",
170 pDrumkit->__info = node->
read_string(
"info",
"No information available.",
171 true,
true, bSilent );
174 true,
true, bSilent ),
175 pDrumkit->__author );
176 pDrumkit->set_license( license );
181 pDrumkit->set_image( node->
read_string(
"image",
"",
182 true,
true,
true ) );
185 pDrumkit->__author );
186 pDrumkit->set_image_license( imageLicense );
188 XMLNode componentListNode = node->firstChildElement(
"componentList" );
189 if ( ! componentListNode.isNull() ) {
190 XMLNode componentNode = componentListNode.firstChildElement(
"drumkitComponent" );
191 while ( ! componentNode.isNull() ) {
193 &componentNode, pLegacyFormatEncountered );
194 if ( pDrumkitComponent !=
nullptr ) {
195 pDrumkit->get_components()->push_back(pDrumkitComponent);
198 componentNode = componentNode.nextSiblingElement(
"drumkitComponent" );
202 auto pDrumkitComponent = std::make_shared<DrumkitComponent>( 0,
"Main" );
203 pDrumkit->get_components()->push_back(pDrumkitComponent);
205 if ( pLegacyFormatEncountered !=
nullptr ) {
206 *pLegacyFormatEncountered =
true;
211 node, sDrumkitPath, sDrumkitName, license, pLegacyFormatEncountered,
214 if ( pInstrumentList ==
nullptr ) {
215 WARNINGLOG(
"instrument list could not be loaded. Using empty one." );
216 pInstrumentList = std::make_shared<InstrumentList>();
218 if ( pLegacyFormatEncountered !=
nullptr ) {
219 *pLegacyFormatEncountered =
true;
223 pDrumkit->set_instruments( pInstrumentList );
228 pDrumkit->propagateLicense();
236 INFOLOG( QString(
"Loading drumkit %1 instrument samples" ).arg(
__name ) );
247 XMLNode root = doc.firstChildElement(
"drumkit_info" );
249 QString sAuthor = root.
read_string(
"author",
"undefined author",
250 true,
true, bSilent );
251 QString sLicenseString = root.
read_string(
"license",
"undefined license",
252 false,
true, bSilent );
253 if ( sLicenseString.isNull() ) {
254 ERRORLOG( QString(
"Unable to retrieve license information from [%1]" )
255 .arg( sDrumkitDir ) );
259 return std::move(
License( sLicenseString, sAuthor ) );
268 ERRORLOG( QString(
"[%1] is not valid drumkit folder" ).arg( sDrumkitDir ) );
274 if ( ! pDoc->
read( sDrumkitPath, bSilent ) ) {
275 ERRORLOG( QString(
"Unable to load drumkit name for [%1]" )
276 .arg( sDrumkitPath ) );
280 XMLNode root = pDoc->firstChildElement(
"drumkit_info" );
281 if ( root.isNull() ) {
282 ERRORLOG( QString(
"Unable to load drumkit name for [%1]. 'drumkit_info' node not found" )
283 .arg( sDrumkitPath ) );
292 if ( pDrumkit !=
nullptr ) {
295 ERRORLOG( QString(
"No drumkit.xml found in folder [%1]" ).arg( sDrumkitPath ) );
300 ERRORLOG( QString(
"Drumkit in [%1] is out of date but can not be upgraded since path is not writable (please copy it to your user's home instead)" ).arg( sDrumkitPath ) );
304 INFOLOG( QString(
"Upgrading drumkit [%1]" ).arg( sDrumkitPath ) );
313 pDrumkit->save( sDrumkitPath, -1,
true, bSilent );
319 INFOLOG( QString(
"Unloading drumkit %1 instrument samples" ).arg(
__name ) );
332 if ( ! sComponentName.isEmpty() ) {
333 sExportName.append(
"_" +
335 if ( ! bRecentVersion ) {
336 sExportName.append(
"_legacy" );
343bool Drumkit::save(
const QString& sDrumkitPath,
int nComponentID,
bool bRecentVersion,
bool bSilent )
345 QString sDrumkitFolder( sDrumkitPath );
346 if ( sDrumkitPath.isEmpty() ) {
355 QFileInfo fi( sDrumkitPath );
357 WARNINGLOG( QString(
"Please provide the path to the drumkit folder instead to the drumkit.xml file within: [%1]" )
358 .arg( sDrumkitPath ) );
359 sDrumkitFolder = fi.dir().absolutePath();
365 ERRORLOG( QString(
"Unable to export drumkit [%1] to [%2]. Could not create drumkit folder." )
366 .arg(
__name ).arg( sDrumkitFolder ) );
372 ERRORLOG( QString(
"Unable to export drumkit [%1] to [%2]. Drumkit folder not writable." )
373 .arg(
__name ).arg( sDrumkitFolder ) );
378 INFOLOG( QString(
"Saving drumkit [%1] into [%2]" )
379 .arg(
__name ).arg( sDrumkitFolder ) );
384 ERRORLOG( QString(
"Unable to save samples of drumkit [%1] to [%2]. Abort." )
385 .arg(
__name ).arg( sDrumkitFolder ) );
389 if ( !
save_image( sDrumkitFolder, bSilent ) ) {
390 ERRORLOG( QString(
"Unable to save image of drumkit [%1] to [%2]. Abort." )
391 .arg(
__name ).arg( sDrumkitFolder ) );
411 save_to( &root, nComponentID, bRecentVersion, bSilent );
428 if ( bRecentVersion ) {
432 pComponent->save_to( &components_node );
436 bool bComponentFound =
false;
438 if ( component_id != -1 ) {
440 if ( pComponent !=
nullptr &&
441 pComponent->get_id() == component_id ) {
442 bComponentFound =
true;
443 pComponent->save_to( &components_node );
447 WARNINGLOG(
"Drumkit has no components. Storing an empty one as fallback." );
450 if ( ! bComponentFound ) {
451 if ( component_id != -1 ) {
452 ERRORLOG( QString(
"Unable to retrieve DrumkitComponent [%1]. Storing an empty one as fallback." )
453 .arg( component_id ) );
455 auto pDrumkitComponent = std::make_shared<DrumkitComponent>( 0,
"Main" );
456 pDrumkitComponent->save_to( &components_node );
461 if ( component_id == -1 ) {
462 ERRORLOG(
"Exporting the full drumkit with all components is allowed when targeting the legacy versions >= 0.9.6" );
468 __instruments->save_to( node, component_id, bRecentVersion,
false );
470 WARNINGLOG(
"Drumkit has no instruments. Storing an InstrumentList with a single empty Instrument as fallback." );
471 auto pInstrumentList = std::make_shared<InstrumentList>();
472 auto pInstrument = std::make_shared<Instrument>();
473 pInstrumentList->insert( 0, pInstrument );
474 pInstrumentList->save_to( node, component_id, bRecentVersion );
481 INFOLOG( QString(
"Saving drumkit [%1] samples into [%2]" )
482 .arg(
__name ).arg( sDrumkitFolder ) );
486 for (
int i = 0; i < pInstrList->size(); i++ ) {
487 auto pInstrument = ( *pInstrList )[i];
488 for (
const auto& pComponent : *pInstrument->get_components() ) {
489 if ( pComponent ==
nullptr ) {
493 auto pLayer = pComponent->get_layer( n );
494 if ( pLayer !=
nullptr && pLayer->get_sample() !=
nullptr ) {
495 QString src = pLayer->get_sample()->get_filepath();
496 QString dst = sDrumkitFolder +
"/" + pLayer->get_sample()->get_filename();
499 QString original_dst = dst;
502 int insertPosition = original_dst.length();
503 if ( original_dst.lastIndexOf(
".") > 0 ) {
504 insertPosition = original_dst.lastIndexOf(
".");
507 pLayer->get_sample()->set_filename( dst );
525 QString dst = sDrumkitDir +
"/" +
__image;
528 ERRORLOG( QString(
"Error copying %1 to %2").arg( src ).arg( dst ) );
539 if ( pComponent->get_id() == nID ) {
550 bool bIsPresent =
false;
552 if ( ppComp !=
nullptr && ppComp->get_id() == ii ) {
569 if ( pComponent ==
nullptr ) {
575 if ( ppComponent == pComponent ) {
576 ERRORLOG(
"Component is already present" );
584 ppInstrument->get_components()->push_back(
585 std::make_shared<InstrumentComponent>(pComponent->get_id()) );
590 if ( pInstrument ==
nullptr ) {
597 for (
const auto& ppInstrumentComponent : *pInstrument->get_components() ) {
598 if ( ppInstrumentComponent ==
nullptr ) {
602 const int nComponentId = ppInstrumentComponent->get_drumkit_componentID();
604 ERRORLOG( QString(
"No component of id [%1] found! Creating a new one" )
605 .arg( nComponentId ) );
608 nComponentId, QString::number( nComponentId ) ) );
613 for (
const auto& ppThisKitsComponent : *
__components ) {
614 if ( ppThisKitsComponent !=
nullptr ) {
615 bool bIsPresent =
false;
616 for (
const auto& ppInstrumentCompnent : *pInstrument->get_components() ) {
617 if ( ppInstrumentCompnent !=
nullptr &&
618 ppInstrumentCompnent->get_drumkit_componentID() ==
619 ppThisKitsComponent->get_id() ) {
626 auto pNewInstrCompo = std::make_shared<InstrumentComponent>(
627 ppThisKitsComponent->get_id() );
628 pInstrument->get_components()->push_back( pNewInstrCompo );
635 bool bIdAlreadyPresent =
false;
637 if ( ppInstrument !=
nullptr &&
638 ppInstrument->get_id() == pInstrument->get_id() ) {
639 bIdAlreadyPresent =
true;
643 if ( bIdAlreadyPresent && pInstrument->get_id() >= 0 ) {
646 bool bIsPresent =
false;
648 if ( ppInstrument !=
nullptr &&
649 ppInstrument->get_id() == ii ) {
655 if ( ! bIsPresent ) {
661 pInstrument->set_id( nNewId );
680 if ( ppInstrument !=
nullptr ) {
682 ppInstrument->set_drumkit_path(
__path );
683 ppInstrument->set_drumkit_name(
__name );
684 for (
const auto& ppInstrumentComponent : *ppInstrument->get_components() ) {
685 if ( ppInstrumentComponent !=
nullptr ) {
686 for (
const auto& ppInstrumentLayer : *ppInstrumentComponent ) {
687 if ( ppInstrumentLayer !=
nullptr ) {
688 auto pSample = ppInstrumentLayer->get_sample();
689 if ( pSample !=
nullptr ) {
707 ERRORLOG( QString(
"%1 is not valid drumkit folder" ).arg( sDrumkitDir ) );
711 INFOLOG( QString(
"Removing drumkit: %1" ).arg( sDrumkitDir ) );
713 ERRORLOG( QString(
"Unable to remove drumkit: %1" ).arg( sDrumkitDir ) );
722 QString* pInstalledPath,
bool* pEncodingIssuesDetected,
726 if ( pInstalledPath !=
nullptr ) {
727 *pInstalledPath =
"";
729 if ( pEncodingIssuesDetected !=
nullptr ) {
730 *pEncodingIssuesDetected =
false;
733 if ( sTargetPath.isEmpty() ) {
735 INFOLOG( QString(
"Install drumkit [%1]" ).arg( sSourcePath ) );
744 INFOLOG( QString(
"Extract drumkit from [%1] to [%2]" )
745 .arg( sSourcePath ).arg( sTargetPath ) );
749#ifdef H2CORE_HAVE_LIBARCHIVE
752 bool bUseUtf8Encoding =
true;
753 if (
nullptr == setlocale( LC_ALL,
"en_US.UTF-8" ) ) {
754 INFOLOG(
"No en_US.UTF-8 locale not available on this system" );
755 bUseUtf8Encoding =
false;
759 struct archive_entry* entry;
762 INFOLOG( QString(
"Importing using `libarchive` version [%1]" )
763 .arg( ARCHIVE_VERSION_STRING ) );
766 a = archive_read_new();
767 if ( a ==
nullptr ) {
768 ERRORLOG(
"Unable to create new archive" );
772#if ARCHIVE_VERSION_NUMBER < 3000000
773 archive_read_support_compression_all( a );
775 nRet = archive_read_support_filter_all( a );
776 if ( nRet != ARCHIVE_OK ) {
777 WARNINGLOG( QString(
"Couldn't add support for all filters: %1" )
778 .arg( archive_error_string( a ) ) );
782 nRet = archive_read_support_format_all( a );
783 if ( nRet != ARCHIVE_OK ) {
784 WARNINGLOG( QString(
"Couldn't add support for all formats: %1" )
785 .arg( archive_error_string( a ) ) );
790 auto tearDown = [&]() {
791 archive_read_close( a );
793#if ARCHIVE_VERSION_NUMBER < 3000000
794 archive_read_finish( a );
796 archive_read_free( a );
800#if ARCHIVE_VERSION_NUMBER < 3000000
801 const auto sSourcePathUtf8 = sSourcePath.toUtf8();
802 nRet = archive_read_open_file( a, sSourcePathUtf8.constData(), 10240 );
805 QString sSourcePathPadded = sSourcePath;
806 sSourcePathPadded.append(
'\0' );
807 auto sourcePathW = sSourcePathPadded.toStdWString();
808 nRet = archive_read_open_filename_w( a, sourcePathW.c_str(), 10240 );
810 const auto sSourcePathUtf8 = sSourcePath.toUtf8();
811 nRet = archive_read_open_filename( a, sSourcePathUtf8.constData(),
815 if ( nRet != ARCHIVE_OK ) {
816 ERRORLOG( QString(
"Unable to open archive [%1] for reading: %2" )
818 .arg( archive_error_string( a ) ) );
824 if ( ! sTargetPath.isEmpty() ) {
825 sDrumkitDir = sTargetPath +
"/";
830 while ( ( nRet = archive_read_next_header( a, &entry ) ) != ARCHIVE_EOF ) {
831 if ( nRet != ARCHIVE_OK ) {
832 ERRORLOG( QString(
"Unable to read next archive header: %1" )
833 .arg( archive_error_string( a ) ) );
837 if ( entry ==
nullptr ) {
838 ERRORLOG(
"Couldn't read in next archive entry" );
842 QString sNewPath = QString::fromUtf8( archive_entry_pathname_utf8( entry ) );
843 if ( sNewPath.isEmpty() ) {
844 sNewPath = QString( archive_entry_pathname( entry ) );
847 if ( ! bUseUtf8Encoding ) {
853 if ( sNewPathTrimmed != sNewPath ) {
854 ERRORLOG( QString(
"Encoding error (no UTF-8 available)! File was renamed [%1] -> [%2]" )
855 .arg( sNewPath ).arg( sNewPathTrimmed ) );
856 if ( pEncodingIssuesDetected !=
nullptr ) {
857 *pEncodingIssuesDetected =
true;
859 sNewPath = sNewPathTrimmed;
862 sNewPath.prepend( sDrumkitDir );
864 if ( pInstalledPath !=
nullptr &&
868 QFileInfo installInfo( sNewPath );
869 *pInstalledPath = installInfo.absoluteDir().absolutePath();
871 QByteArray newpath = sNewPath.toUtf8();
873 archive_entry_set_pathname( entry, newpath.data() );
874 nRet = archive_read_extract( a, entry, 0 );
875 if ( nRet == ARCHIVE_WARN ) {
876 WARNINGLOG( QString(
"While extracting content of [%1] from archive: %2" )
878 .arg( archive_error_string( a ) ) );
880 else if ( nRet != ARCHIVE_OK ) {
881 ERRORLOG( QString(
"Unable to extract content of [%1] from archive: %2" )
883 .arg( archive_error_string( a ) ) );
888 nRet = archive_read_close( a );
889 if ( nRet != ARCHIVE_OK ) {
890 ERRORLOG( QString(
"Couldn't close archive: %1" )
891 .arg( archive_error_string( a ) ) );
895#if ARCHIVE_VERSION_NUMBER < 3000000
896 archive_read_finish( a );
898 nRet = archive_read_free( a );
899 if ( nRet != ARCHIVE_OK ) {
900 WARNINGLOG( QString(
"Couldn't free memory associated with archive: %1" )
901 .arg( archive_error_string( a ) ) );
910 QString gzd_name = sSourcePath.left( sSourcePath.indexOf(
"." ) ) +
".tar";
911 FILE* gzd_file = fopen( gzd_name.toLocal8Bit(),
"wb" );
912 gzFile gzip_file = gzopen( sSourcePath.toLocal8Bit(),
"rb" );
914 _ERRORLOG( QString(
"Error reading drumkit file: %1" )
915 .arg( sSourcePath ) );
916 gzclose( gzip_file );
921 while ( gzread( gzip_file, buf, 4096 ) > 0 ) {
922 fwrite( buf,
sizeof( uchar ), 4096, gzd_file );
924 gzclose( gzip_file );
929 QByteArray tar_path = gzd_name.toLocal8Bit();
931 if ( tar_open( &tar_file, tar_path.data(), NULL, O_RDONLY, 0, TAR_GNU ) == -1 ) {
932 _ERRORLOG( QString(
"tar_open(): %1" ).arg( QString::fromLocal8Bit( strerror( errno ) ) ) );
939 if ( ! sTargetPath.isEmpty() ) {
940 dk_dir = sTargetPath +
"/";
945 strncpy( dst_dir, dk_dir.toLocal8Bit(), 1024 );
946 if ( tar_extract_all( tar_file, dst_dir ) != 0 ) {
947 _ERRORLOG( QString(
"tar_extract_all(): %1" )
948 .arg( QString::fromLocal8Bit( strerror( errno ) ) ) );
951 if ( tar_close( tar_file ) != 0 ) {
953 .arg( QString::fromLocal8Bit( strerror( errno ) ) ) );
965 bool bRecentVersion,
bool* pUtf8Encoded,
bool bSilent ) {
966 if ( pUtf8Encoded !=
nullptr ) {
968 *pUtf8Encoded =
false;
972 ERRORLOG( QString(
"Provided destination folder [%1] is not valid" )
973 .arg( sTargetDir ) );
977 if ( ! bRecentVersion && sComponentName.isEmpty() ) {
978 ERRORLOG(
"A DrumkiComponent name is required to exported a drumkit in a format similar to the one prior to version 0.9.7" );
995 QString sOldDrumkitName =
__name;
996 QString sDrumkitName =
getExportName( sComponentName, bRecentVersion );
998 QString sTargetName = sTargetDir +
"/" + sDrumkitName +
1002 QString sMsg(
"Export ");
1004 if ( sComponentName.isEmpty() && bRecentVersion ) {
1005 sMsg.append(
"drumkit " );
1007 sMsg.append( QString(
"component: [%1] " ).arg( sComponentName ) );
1010 sMsg.append( QString(
"to [%1] " ).arg( sTargetName ) );
1012 if ( bRecentVersion ) {
1013 sMsg.append(
"using the most recent format" );
1015 sMsg.append(
"using the legacy format supported by Hydrogen versions <= 0.9.6" );
1025 if ( ! sComponentName.isEmpty() ) {
1026 tmpFolder.setAutoRemove(
false );
1033 int nComponentID = -1;
1034 if ( ! sComponentName.isEmpty() ) {
1036 if( pComponent->get_name().compare( sComponentName ) == 0) {
1037 nComponentID = pComponent->get_id();
1042 if ( nComponentID == -1 ) {
1043 ERRORLOG( QString(
"Component [%1] could not be found in current Drumkit [%2]" )
1044 .arg( sComponentName )
1049 if ( !
save( tmpFolder.path(), nComponentID, bRecentVersion, bSilent ) ) {
1050 ERRORLOG( QString(
"Unable to save backup drumkit to [%1] using component ID [%2]" )
1051 .arg( tmpFolder.path() ).arg( nComponentID ) );
1056 ERRORLOG( QString(
"Unabled to access folder associated with drumkit [%1]" )
1062 QDir sourceDir(
__path );
1064 QStringList sourceFilesList = sourceDir.entryList( QDir::Files );
1067 QStringList filesUsed;
1080 QStringList suffixBlacklist;
1087 for (
const auto& ssFile : sourceFilesList ) {
1089 nComponentID != -1 ) {
1093 bSampleFound =
false;
1095 if( pInstr !=
nullptr ) {
1096 for (
auto const& pComponent : *( pInstr->get_components() ) ) {
1097 if ( pComponent !=
nullptr &&
1098 ( nComponentID == -1 ||
1099 pComponent->get_drumkit_componentID() == nComponentID ) ) {
1101 const auto pLayer = pComponent->get_layer( n );
1102 if( pLayer !=
nullptr && pLayer->get_sample() !=
nullptr ) {
1103 if( pLayer->get_sample()->get_filename().compare( ssFile ) == 0 ) {
1104 filesUsed << sourceDir.filePath( ssFile );
1105 bSampleFound =
true;
1116 if ( ! bSampleFound ) {
1117 QFileInfo ffileInfo( sourceDir.filePath( ssFile ) );
1118 if ( ! suffixBlacklist.contains( ffileInfo.suffix(),
1119 Qt::CaseInsensitive ) ) {
1125 ssFile.contains(
".bak" ) ) ) {
1126 filesUsed << sourceDir.filePath( ssFile );
1133#if defined(H2CORE_HAVE_LIBARCHIVE)
1136 INFOLOG( QString(
"Exporting using `libarchive` version [%1]" )
1137 .arg( ARCHIVE_VERSION_STRING ) );
1140 bool bUseUtf8Encoding =
true;
1141 if (
nullptr == setlocale( LC_ALL,
"en_US.UTF-8" ) ) {
1142 ERRORLOG(
"No en_US.UTF-8 locale not available on this system" );
1143 bUseUtf8Encoding =
false;
1147 struct archive_entry *entry;
1149 const int nBufferSize = 8192;
1150 char buff[ nBufferSize ];
1151 int nBytesRead, nRet;
1154 if ( pUtf8Encoded !=
nullptr ) {
1155 *pUtf8Encoded = bUseUtf8Encoding;
1158 a = archive_write_new();
1159 if ( a ==
nullptr ) {
1160 ERRORLOG(
"Unable to create new archive" );
1165#if ARCHIVE_VERSION_NUMBER < 3000000
1166 archive_write_set_compression_gzip( a );
1168 nRet = archive_write_add_filter_gzip( a );
1169 if ( nRet != ARCHIVE_OK ) {
1170 ERRORLOG( QString(
"Couldn't add GZIP filter: %1" )
1171 .arg( archive_error_string( a ) ) );
1177 nRet = archive_write_set_format_pax_restricted( a );
1178 if ( nRet != ARCHIVE_OK ) {
1179 ERRORLOG( QString(
"Couldn't set archive format to 'pax restricted': %1" )
1180 .arg( archive_error_string( a ) ) );
1187 QString sTargetNamePadded = QString( sTargetName );
1188 sTargetNamePadded.append(
'\0' );
1189 const auto targetPath = sTargetNamePadded.toStdWString();
1190 nRet = archive_write_open_filename_w( a, targetPath.c_str() );
1192 const auto sTargetNameUtf8 = sTargetName.toUtf8();
1193 const auto targetPath = sTargetNameUtf8.constData();
1194 nRet = archive_write_open_filename( a, targetPath );
1196 if ( nRet != ARCHIVE_OK ) {
1197 ERRORLOG( QString(
"Couldn't create archive [%1]: %2" )
1199 .arg( archive_error_string( a ) ) );
1204 bool bFoundFileInRightComponent;
1205 for (
const auto& sFilename : filesUsed ) {
1206 QFileInfo ffileInfo( sFilename );
1207 QString sTargetFilename = sDrumkitName +
"/" + ffileInfo.fileName();
1213 ERRORLOG( QString(
"Unable to export drumkit. File [%1] does not exists or is not readable." )
1214 .arg( sFilename ) );
1219 const auto sFilenameUtf8 = sFilename.toUtf8();
1220 stat( sFilenameUtf8.constData(), &st );
1221 entry = archive_entry_new();
1222 if ( entry ==
nullptr ) {
1223 ERRORLOG(
"Unable to create new archive entry" );
1228 const auto sTargetFilenameUtf8 = sTargetFilename.toUtf8();
1229#if defined(WIN32) and ARCHIVE_VERSION_NUMBER >= 3005000
1230 if ( bUseUtf8Encoding ) {
1231 archive_entry_set_pathname_utf8(
1232 entry, sTargetFilenameUtf8.constData());
1237 archive_entry_set_pathname(entry, sTargetFilenameUtf8.constData());
1239 archive_entry_set_size(entry, st.st_size);
1240 archive_entry_set_filetype(entry, AE_IFREG);
1241 archive_entry_set_perm(entry, 0644);
1242 nRet = archive_write_header(a, entry);
1243 if ( nRet != ARCHIVE_OK ) {
1244 ERRORLOG( QString(
"Couldn't write entry for [%1] to archive header: %2" )
1246 .arg( archive_error_string( a ) ) );
1251 QFile file( sFilename );
1252 if ( ! file.open( QIODevice::ReadOnly ) ) {
1253 ERRORLOG( QString(
"Unable to open file [%1] for reading" )
1254 .arg( sFilename ) );
1255 archive_entry_free( entry );
1259 QDataStream stream( &file );
1260 nBytesRead = stream.readRawData( buff, nBufferSize );
1261 while ( nBytesRead > 0 ) {
1262 nRet = archive_write_data( a, buff, nBytesRead );
1264 ERRORLOG( QString(
"Error while writing data to entry of [%1]: %2" )
1265 .arg( sFilename ).arg( archive_error_string( a ) ) );
1268 else if ( nRet != nBytesRead ) {
1269 WARNINGLOG( QString(
"Only [%1/%2] bytes written to archive entry of [%3]" )
1270 .arg( nRet ).arg( nBytesRead ).arg( sFilename ) );
1273 nBytesRead = stream.readRawData( buff, nBufferSize );
1276 archive_entry_free(entry);
1278 nRet = archive_write_close(a);
1279 if ( nRet != ARCHIVE_OK ) {
1280 ERRORLOG( QString(
"Couldn't close archive: %1" )
1281 .arg( archive_error_string( a ) ) );
1287#if ARCHIVE_VERSION_NUMBER < 3000000
1288 archive_write_finish(a);
1290 nRet = archive_write_free(a);
1291 if ( nRet != ARCHIVE_OK ) {
1292 WARNINGLOG( QString(
"Couldn't free memory associated with archive: %1" )
1293 .arg( archive_error_string( a ) ) );
1297 sourceFilesList.clear();
1310 if ( nComponentID != -1 ) {
1320 QDir sTmpSourceDir( tmpFolder.path() +
"/" + sDirName );
1321 if ( sTmpSourceDir.exists() ) {
1322 sTmpSourceDir.removeRecursively();
1326 ERRORLOG( QString(
"Unable to create tmp folder [%1]" )
1327 .arg( tmpFolder.path() +
"/" + sDirName ) );
1332 QString sNewFilePath;
1333 QStringList copiedFiles;
1334 for (
const auto& ssFile : filesUsed ) {
1335 QString sNewFilePath( ssFile );
1336 sNewFilePath.replace( sNewFilePath.left( sNewFilePath.lastIndexOf(
"/" ) ),
1337 tmpFolder.path() +
"/" + sDirName );
1339 ERRORLOG( QString(
"Unable to copy file [%1] to [%2]." )
1340 .arg( ssFile ).arg( sNewFilePath ) );
1345 copiedFiles << sNewFilePath;
1348 filesUsed = copiedFiles;
1349 sourceDir = QDir( tmpFolder.path() +
"/" + sDirName );
1358 filesUsed = filesUsed.replaceInStrings( sourceDir.absolutePath(),
1359 sourceDir.dirName() );
1361 QString sCmd = QString(
"tar czf %1 -C %2 -- \"%3\"" )
1363 .arg( sourceDir.absolutePath().left( sourceDir.absolutePath().lastIndexOf(
"/" ) ) )
1364 .arg( filesUsed.join(
"\" \"" ) );
1365 int nRet = std::system( sCmd.toLocal8Bit() );
1368 ERRORLOG( QString(
"Unable to export drumkit using system command:\n%1" )
1383 ERRORLOG(
"Operation not supported on Windows" );
1395 sOutput = QString(
"%1[Drumkit]\n" ).arg( sPrefix )
1396 .append( QString(
"%1%2path: %3\n" ).arg( sPrefix ).arg( s ).arg(
__path ) )
1397 .append( QString(
"%1%2name: %3\n" ).arg( sPrefix ).arg( s ).arg(
__name ) )
1398 .append( QString(
"%1%2author: %3\n" ).arg( sPrefix ).arg( s ).arg(
__author ) )
1399 .append( QString(
"%1%2info: %3\n" ).arg( sPrefix ).arg( s ).arg(
__info ) )
1400 .append( QString(
"%1%2license: %3\n" ).arg( sPrefix ).arg( s ).arg(
__license.toQString() ) )
1401 .append( QString(
"%1%2image: %3\n" ).arg( sPrefix ).arg( s ).arg(
__image ) )
1402 .append( QString(
"%1%2imageLicense: %3\n" ).arg( sPrefix ).arg( s ).arg(
__imageLicense.toQString() ) )
1403 .append( QString(
"%1%2samples_loaded: %3\n" ).arg( sPrefix ).arg( s ).arg(
__samples_loaded ) )
1404 .append( QString(
"%1" ).arg(
__instruments->toQString( sPrefix + s, bShort ) ) )
1405 .append( QString(
"%1%2components:\n" ).arg( sPrefix ).arg( s ) );
1407 if ( cc !=
nullptr ) {
1408 sOutput.append( QString(
"%1" ).arg( cc->toQString( sPrefix + s + s, bShort ) ) );
1413 sOutput = QString(
"[Drumkit]" )
1414 .append( QString(
" path: %1" ).arg(
__path ) )
1415 .append( QString(
", name: %1" ).arg(
__name ) )
1416 .append( QString(
", author: %1" ).arg(
__author ) )
1417 .append( QString(
", info: %1" ).arg(
__info ) )
1418 .append( QString(
", license: %1" ).arg(
__license.toQString() ) )
1419 .append( QString(
", image: %1" ).arg(
__image ) )
1420 .append( QString(
", imageLicense: %1" ).arg(
__imageLicense.toQString() ) )
1422 .append( QString(
", [%1]" ).arg(
__instruments->toQString( sPrefix + s, bShort ) ) )
1423 .append( QString(
", components: [ " ) );
1425 if ( cc !=
nullptr ) {
1426 sOutput.append( QString(
"[%1]" ).arg( cc->toQString( sPrefix + s + s, bShort ).replace(
"\n",
" " ) ) );
1429 sOutput.append(
"]\n" );
static QString sPrintIndention
String used to format the debugging string output of some core classes.
static std::shared_ptr< DrumkitComponent > load_from(XMLNode *node, bool *pLegacyFormatEncountered=nullptr)
QString __name
drumkit name
int findUnusedComponentId() const
static bool loadDoc(const QString &sDrumkitDir, XMLDoc *pDoc, bool bSilent=false)
Loads the drumkit stored in sDrumkitDir into pDoc and takes care of all the error handling.
const QString & get_info() const
__info accessor
std::shared_ptr< InstrumentList > get_instruments() const
returns __instruments
void set_name(const QString &name)
__name setter
const License & get_license() const
__license accessor
const QString & get_name() const
__name accessor
void save_to(XMLNode *node, int component_id=-1, bool bRecentVersion=true, bool bSilent=false) const
static bool install(const QString &sSourcePath, const QString &sTargetPath="", QString *pInstalledPath=nullptr, bool *pEncodingIssuesDetected=nullptr, bool bSilent=false)
Extract a .h2drumkit file.
bool exportTo(const QString &sTargetDir, const QString &sComponentName="", bool bRecentVersion=true, bool *pUtf8Encoded=nullptr, bool bSilent=false)
Compresses the drumkit into a .h2drumkit file.
bool save_image(const QString &dk_dir, bool bSilent=false) const
save the drumkit image into the new directory
std::shared_ptr< std::vector< std::shared_ptr< DrumkitComponent > > > __components
list of drumkit component
const QString & get_image() const
__image accessor
void propagateLicense()
Assign the license stored in #m_license to all samples contained in the kit.
License __imageLicense
drumkit image license
~Drumkit()
drumkit destructor, delete __instruments
static void upgrade_drumkit(std::shared_ptr< Drumkit > pDrumkit, const QString &dk_path, bool bSilent=false)
Saves the current drumkit to dk_path, but makes a backup.
std::shared_ptr< InstrumentList > __instruments
the list of instruments
QString __info
drumkit free text
bool save_samples(const QString &dk_dir, bool bSilent=false) const
save a drumkit instruments samples into a directory
void load_samples()
Calls the InstrumentList::load_samples() member function of __instruments.
QString __author
drumkit author
static bool remove(const QString &sDrumkitDir)
remove a drumkit from the disk
static std::shared_ptr< Drumkit > load(const QString &dk_dir, bool bUpgrade=true, bool *pLegacyFormatEncountered=nullptr, bool bSilent=false)
Load drumkit information from a directory.
const QString & get_author() const
__author accessor
std::shared_ptr< DrumkitComponent > getComponent(int nID) const
std::vector< std::shared_ptr< InstrumentList::Content > > summarizeContent() const
Returns vector of lists containing instrument name, component name, file name, the license of all ass...
QString __image
drumkit image filename
void addComponent(std::shared_ptr< DrumkitComponent > pComponent)
static std::shared_ptr< Drumkit > load_from(XMLNode *node, const QString &dk_path, bool *pLegacyFormatEncountered=nullptr, bool bSilent=false)
load a drumkit from an XMLNode
static License loadLicenseFrom(const QString &sDrumkitDir, bool bSilent=false)
Loads the license information of a drumkit contained in directory sDrumkitDir.
QString getExportName(const QString &sComponentName, bool bRecentVersion) const
Returns the base name used when exporting the drumkit.
QString toQString(const QString &sPrefix="", bool bShort=true) const override
Formatted string version for debugging purposes.
const bool samples_loaded() const
return true if the samples are loaded
void addInstrument(std::shared_ptr< Instrument > pInstrument)
Adds an instrument and takes care of registering DrumkitComponents missing for contained InstrumentCo...
QString __path
absolute drumkit path
void unload_samples()
Calls the InstrumentList::unload_samples() member function of __instruments.
const QString & get_path() const
__path accessor
Drumkit()
drumkit constructor, does nothing
bool __samples_loaded
true if the instrument samples are loaded
bool save(const QString &sDrumkitPath="", int nComponentID=-1, bool bRecentVersion=true, bool bSilent=false)
Save a drumkit to disk.
QString getFolderName() const
Returns a version of __name stripped of all whitespaces and other characters which would prevent its ...
void set_components(std::shared_ptr< std::vector< std::shared_ptr< DrumkitComponent > > > components)
void set_instruments(std::shared_ptr< InstrumentList > instruments)
set __instruments, delete existing one
License __license
drumkit license description
const License & get_image_license() const
__imageLicense accessor
static bool file_copy(const QString &src, const QString &dst, bool overwrite=false, bool bSilent=false)
copy a source file to a destination
static QString validateFilePath(const QString &sPath)
Takes an arbitrary path, replaces white spaces by underscores and removes all characters apart from l...
static bool dir_readable(const QString &path, bool silent=false)
returns true if the given path is a readable regular directory
static QString removeUtf8Characters(const QString &sEncodedString)
Removes all characters not within the Latin-1 range of sEncodedString.
static bool drumkit_valid(const QString &dk_path)
returns true if the path contains a usable drumkit
static QString usr_drumkits_dir()
returns user drumkits path
static bool rm(const QString &path, bool recursive=false, bool bSilent=false)
remove a path
static QString drumkit_backup_path(const QString &dk_path)
Create a backup path from a drumkit path.
static bool dir_writable(const QString &path, bool silent=false)
returns true if the given path is a writable regular directory
static bool file_exists(const QString &path, bool silent=false)
returns true if the given path is an existing regular file
static const std::vector< AudioFormat > & supportedAudioFormats()
Which format is supported is determined by the libsndfile version Hydrogen is linked against during c...
static bool mkdir(const QString &path)
create a path
static QString drumkit_file(const QString &dk_path)
returns the path to the xml file within a supposed drumkit path
static QString tmp_dir()
returns temp path
static const QString drumkit_ext
static bool path_usable(const QString &path, bool create=true, bool silent=false)
returns true if the path is a readable and writable regular directory, create if it not exists
static QString AudioFormatToSuffix(const AudioFormat &format, bool bSilent=false)
Converts format to the default lower case suffix of the format.
static QString drumkit_xml()
Returns filename and extension of the expected drumkit file.
static bool file_readable(const QString &path, bool silent=false)
returns true if the given path is an existing readable regular file
static bool dir_exists(const QString &path, bool silent=false)
returns true if the given path is a regular directory
static std::shared_ptr< H2Core::Drumkit > loadDrumkit(XMLNode &node, const QString &sDrumkitPath, bool bSilent=false)
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
SoundLibraryDatabase * getSoundLibraryDatabase() const
static int getMaxLayers()
static std::shared_ptr< InstrumentList > load_from(XMLNode *node, const QString &sDrumkitPath, const QString &sDrumkitName, const License &license=License(), bool *pLegacyFormatEncountered=nullptr, bool bSilent=false)
load an instrument list from an XMLNode
Wrapper class to help Hydrogen deal with the license information specified in a drumkit.
@ GPL
Not a desirable license for audio data but introduced here specifically since it is already used by a...
static QString getGPLLicenseNotice(const QString &sAuthor)
void updateDrumkits(bool bTriggerEvent=true)
XMLDoc is a subclass of QDomDocument with read and write methods.
XMLNode set_root(const QString &node_name, const QString &xmlns=nullptr)
create the xml header and root node
bool read(const QString &filepath, bool bSilent=false)
read the content of an xml file
bool write(const QString &filepath)
write itself into a file
XMLNode is a subclass of QDomNode with read and write values methods.
QString read_string(const QString &node, const QString &default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads a string stored into a child node
XMLNode createNode(const QString &name)
create a new XMLNode that has to be appended into de XMLDoc
void write_string(const QString &node, const QString &value)
write a string into a child node