hydrogen 1.2.6
Drumkit.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
23#include <QFile>
24#include <QDataStream>
25
26#include <core/Basics/Drumkit.h>
27#include <core/config.h>
28#ifdef H2CORE_HAVE_LIBARCHIVE
29#include <archive.h>
30#include <archive_entry.h>
31#else
32 #ifndef WIN32
33#include <fcntl.h>
34#include <errno.h>
35#include <zlib.h>
36#include <libtar.h>
37 #endif
38#endif
39
40#include <core/Basics/Sample.h>
46
47#include <core/Helpers/Future.h>
48#include <core/Helpers/Legacy.h>
49#include <core/Helpers/Xml.h>
50
51#include <core/Hydrogen.h>
53
54namespace H2Core
55{
56
58 __instruments( nullptr ),
59 __name( "empty" ),
60 __author( "undefined author" ),
61 __info( "No information available." ),
62 __license( License() ),
64{
65 QDir usrDrumkitPath( Filesystem::usr_drumkits_dir() );
66 __path = usrDrumkitPath.filePath( __name );
67 __components = std::make_shared<std::vector<std::shared_ptr<DrumkitComponent>>>();
68 __instruments = std::make_shared<InstrumentList>();
69}
70
71Drumkit::Drumkit( std::shared_ptr<Drumkit> other ) :
72 Object(),
73 __path( other->get_path() ),
74 __name( other->get_name() ),
75 __author( other->get_author() ),
76 __info( other->get_info() ),
77 __license( other->get_license() ),
78 __image( other->get_image() ),
81{
82 __instruments = std::make_shared<InstrumentList>( other->get_instruments() );
83
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 ) );
87 }
88}
89
93
94std::shared_ptr<Drumkit> Drumkit::load( const QString& sDrumkitPath,
95 bool bUpgrade,
96 bool* pLegacyFormatEncountered,
97 bool bSilent )
98{
99 if ( ! Filesystem::drumkit_valid( sDrumkitPath ) ) {
100 ERRORLOG( QString( "[%1] is not valid drumkit folder" ).arg( sDrumkitPath ) );
101 return nullptr;
102 }
103
104 QString sDrumkitFile = Filesystem::drumkit_file( sDrumkitPath );
105
106 XMLDoc doc;
107 doc.read( sDrumkitFile, bSilent );
108
109 XMLNode root = doc.firstChildElement( "drumkit_info" );
110 if ( root.isNull() ) {
111 ERRORLOG( "drumkit_info node not found" );
112 return nullptr;
113 }
114
115 bool bLegacyFormatEncountered = false;
116
117 std::shared_ptr<Drumkit> pDrumkit = nullptr;
118
119 const QString sDrumkitDir =
120 sDrumkitFile.left( sDrumkitFile.lastIndexOf( "/" ) );
121
122 // Check whether the file was created using a newer version of Hydrogen.
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 ) );
127 // Even in case the future version is invalid with respect to the XSD
128 // file, the most recent version of the format will be the most
129 // successful one.
130 pDrumkit = Future::loadDrumkit( root, sDrumkitDir, bSilent );
131 }
132 else {
133 pDrumkit = Drumkit::load_from( &root, sDrumkitDir,
134 &bLegacyFormatEncountered, bSilent );
135 }
136
137 if ( pLegacyFormatEncountered != nullptr ) {
138 *pLegacyFormatEncountered = bLegacyFormatEncountered;
139 }
140
141 if ( pDrumkit == nullptr ) {
142 ERRORLOG( QString( "Unable to load drumkit [%1]" ).arg( sDrumkitFile ) );
143 return nullptr;
144 }
145
146 if ( bLegacyFormatEncountered && bUpgrade ) {
147 upgrade_drumkit( pDrumkit, sDrumkitPath );
148 }
149
150 return pDrumkit;
151}
152
153std::shared_ptr<Drumkit> Drumkit::load_from( XMLNode* node,
154 const QString& sDrumkitPath,
155 bool* pLegacyFormatEncountered,
156 bool bSilent )
157{
158 QString sDrumkitName = node->read_string( "name", "", false, false, bSilent );
159 if ( sDrumkitName.isEmpty() ) {
160 ERRORLOG( "Drumkit has no name, abort" );
161 return nullptr;
162 }
163
164 std::shared_ptr<Drumkit> pDrumkit = std::make_shared<Drumkit>();
165
166 pDrumkit->__path = sDrumkitPath;
167 pDrumkit->__name = sDrumkitName;
168 pDrumkit->__author = node->read_string( "author", "undefined author",
169 true, true, true );
170 pDrumkit->__info = node->read_string( "info", "No information available.",
171 true, true, bSilent );
172
173 License license( node->read_string( "license", "undefined license",
174 true, true, bSilent ),
175 pDrumkit->__author );
176 pDrumkit->set_license( license );
177
178 // As of 2022 we have no drumkits featuring an image in
179 // stock. Thus, verbosity of this one will be turned of in order
180 // to make to log more concise.
181 pDrumkit->set_image( node->read_string( "image", "",
182 true, true, true ) );
183 License imageLicense( node->read_string( "imageLicense", "undefined license",
184 true, true, true ),
185 pDrumkit->__author );
186 pDrumkit->set_image_license( imageLicense );
187
188 XMLNode componentListNode = node->firstChildElement( "componentList" );
189 if ( ! componentListNode.isNull() ) {
190 XMLNode componentNode = componentListNode.firstChildElement( "drumkitComponent" );
191 while ( ! componentNode.isNull() ) {
192 auto pDrumkitComponent = DrumkitComponent::load_from(
193 &componentNode, pLegacyFormatEncountered );
194 if ( pDrumkitComponent != nullptr ) {
195 pDrumkit->get_components()->push_back(pDrumkitComponent);
196 }
197
198 componentNode = componentNode.nextSiblingElement( "drumkitComponent" );
199 }
200 } else {
201 WARNINGLOG( "componentList node not found" );
202 auto pDrumkitComponent = std::make_shared<DrumkitComponent>( 0, "Main" );
203 pDrumkit->get_components()->push_back(pDrumkitComponent);
204
205 if ( pLegacyFormatEncountered != nullptr ) {
206 *pLegacyFormatEncountered = true;
207 }
208 }
209
210 auto pInstrumentList = InstrumentList::load_from(
211 node, sDrumkitPath, sDrumkitName, license, pLegacyFormatEncountered,
212 false );
213 // Required to assure backward compatibility.
214 if ( pInstrumentList == nullptr ) {
215 WARNINGLOG( "instrument list could not be loaded. Using empty one." );
216 pInstrumentList = std::make_shared<InstrumentList>();
217
218 if ( pLegacyFormatEncountered != nullptr ) {
219 *pLegacyFormatEncountered = true;
220 }
221 }
222
223 pDrumkit->set_instruments( pInstrumentList );
224
225 // Instead of making the *::load_from() functions more complex by
226 // passing the license down to each sample, we will make the
227 // drumkit assign its license to each sample in here.
228 pDrumkit->propagateLicense();
229
230 return pDrumkit;
231
232}
233
235{
236 INFOLOG( QString( "Loading drumkit %1 instrument samples" ).arg( __name ) );
237 if( !__samples_loaded ) {
238 __instruments->load_samples();
239 __samples_loaded = true;
240 }
241}
242
243License Drumkit::loadLicenseFrom( const QString& sDrumkitDir, bool bSilent )
244{
245 XMLDoc doc;
246 if ( Drumkit::loadDoc( sDrumkitDir, &doc, bSilent ) ) {
247 XMLNode root = doc.firstChildElement( "drumkit_info" );
248
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 ) );
256 return std::move( License() );
257 }
258
259 return std::move( License( sLicenseString, sAuthor ) );
260 }
261
262 return std::move( License() );
263}
264
265bool Drumkit::loadDoc( const QString& sDrumkitDir, XMLDoc* pDoc, bool bSilent ) {
266
267 if ( ! Filesystem::drumkit_valid( sDrumkitDir ) ) {
268 ERRORLOG( QString( "[%1] is not valid drumkit folder" ).arg( sDrumkitDir ) );
269 return false;
270 }
271
272 const QString sDrumkitPath = Filesystem::drumkit_file( sDrumkitDir );
273
274 if ( ! pDoc->read( sDrumkitPath, bSilent ) ) {
275 ERRORLOG( QString( "Unable to load drumkit name for [%1]" )
276 .arg( sDrumkitPath ) );
277 return false;
278 }
279
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 ) );
284 return false;
285 }
286
287 return true;
288}
289
290void Drumkit::upgrade_drumkit(std::shared_ptr<Drumkit> pDrumkit, const QString& sDrumkitPath, bool bSilent )
291{
292 if ( pDrumkit != nullptr ) {
293 const QString sDrumkitFile = Filesystem::drumkit_file( sDrumkitPath );
294 if ( ! Filesystem::file_exists( sDrumkitFile, true ) ) {
295 ERRORLOG( QString( "No drumkit.xml found in folder [%1]" ).arg( sDrumkitPath ) );
296 return;
297 }
298
299 if ( ! Filesystem::dir_writable( sDrumkitPath, true ) ) {
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 ) );
301 return;
302 }
303 if ( ! bSilent ) {
304 INFOLOG( QString( "Upgrading drumkit [%1]" ).arg( sDrumkitPath ) );
305 }
306
307 QString sBackupFile = Filesystem::drumkit_backup_path( sDrumkitFile );
308 Filesystem::file_copy( sDrumkitFile, sBackupFile,
309 false /* do not overwrite existing
310 files */,
311 bSilent );
312
313 pDrumkit->save( sDrumkitPath, -1, true, bSilent );
314 }
315}
316
318{
319 INFOLOG( QString( "Unloading drumkit %1 instrument samples" ).arg( __name ) );
320 if( __samples_loaded ) {
321 __instruments->unload_samples();
322 __samples_loaded = false;
323 }
324}
325
326QString Drumkit::getFolderName() const {
328}
329
330QString Drumkit::getExportName( const QString& sComponentName, bool bRecentVersion ) const {
331 QString sExportName = getFolderName();
332 if ( ! sComponentName.isEmpty() ) {
333 sExportName.append( "_" +
334 Filesystem::validateFilePath( sComponentName ) );
335 if ( ! bRecentVersion ) {
336 sExportName.append( "_legacy" );
337 }
338 }
339
340 return sExportName;
341}
342
343bool Drumkit::save( const QString& sDrumkitPath, int nComponentID, bool bRecentVersion, bool bSilent )
344{
345 QString sDrumkitFolder( sDrumkitPath );
346 if ( sDrumkitPath.isEmpty() ) {
347 sDrumkitFolder = __path;
348 }
349 else {
350 // We expect the path to a folder in sDrumkitPath. But in case
351 // the user or developer provided the path to the drumkit.xml
352 // file within this folder we don't play dumb as such things
353 // happen and are plausible when just looking at the
354 // function's signature
355 QFileInfo fi( sDrumkitPath );
356 if ( fi.isFile() && fi.fileName() == Filesystem::drumkit_xml() ) {
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();
360 }
361 }
362
363 if ( ! Filesystem::dir_exists( sDrumkitFolder, true ) &&
364 ! Filesystem::mkdir( sDrumkitFolder ) ) {
365 ERRORLOG( QString( "Unable to export drumkit [%1] to [%2]. Could not create drumkit folder." )
366 .arg( __name ).arg( sDrumkitFolder ) );
367 return false;
368 }
369
370 if ( Filesystem::dir_exists( sDrumkitFolder, bSilent ) &&
371 ! Filesystem::dir_writable( sDrumkitFolder, bSilent ) ) {
372 ERRORLOG( QString( "Unable to export drumkit [%1] to [%2]. Drumkit folder not writable." )
373 .arg( __name ).arg( sDrumkitFolder ) );
374 return false;
375 }
376
377 if ( ! bSilent ) {
378 INFOLOG( QString( "Saving drumkit [%1] into [%2]" )
379 .arg( __name ).arg( sDrumkitFolder ) );
380 }
381
382 // Save external files
383 if ( ! save_samples( sDrumkitFolder, bSilent ) ) {
384 ERRORLOG( QString( "Unable to save samples of drumkit [%1] to [%2]. Abort." )
385 .arg( __name ).arg( sDrumkitFolder ) );
386 return false;
387 }
388
389 if ( ! save_image( sDrumkitFolder, bSilent ) ) {
390 ERRORLOG( QString( "Unable to save image of drumkit [%1] to [%2]. Abort." )
391 .arg( __name ).arg( sDrumkitFolder ) );
392 return false;
393 }
394
395 // Ensure all instruments and associated samples will hold the
396 // same license as the overall drumkit and are associated to
397 // it. (Not important for saving itself but for consistency and
398 // using the drumkit later on).
400
401 // Save drumkit.xml
402 XMLDoc doc;
403 XMLNode root = doc.set_root( "drumkit_info", "drumkit" );
404
405 // In order to comply with the GPL license we have to add a
406 // license notice to the file.
407 if ( __license.getType() == License::GPL ) {
408 root.appendChild( doc.createComment( License::getGPLLicenseNotice( __author ) ) );
409 }
410
411 save_to( &root, nComponentID, bRecentVersion, bSilent );
412 return doc.write( Filesystem::drumkit_file( sDrumkitFolder ) );
413}
414
415void Drumkit::save_to( XMLNode* node, int component_id, bool bRecentVersion, bool bSilent ) const
416{
417 node->write_string( "name", __name );
418 node->write_string( "author", __author );
419 node->write_string( "info", __info );
420 node->write_string( "license", __license.getLicenseString() );
421 node->write_string( "image", __image );
422 node->write_string( "imageLicense", __imageLicense.getLicenseString() );
423
424 // Only drumkits used for Hydrogen v0.9.7 or higher are allowed to
425 // have components. If the user decides to export the kit to
426 // legacy version, the components will be omitted and Instrument
427 // layers corresponding to component_id will be exported.
428 if ( bRecentVersion ) {
429 XMLNode components_node = node->createNode( "componentList" );
430 if ( component_id == -1 && __components->size() > 0 ) {
431 for ( const auto& pComponent : *__components ){
432 pComponent->save_to( &components_node );
433 }
434 }
435 else {
436 bool bComponentFound = false;
437
438 if ( component_id != -1 ) {
439 for ( const auto& pComponent : *__components ){
440 if ( pComponent != nullptr &&
441 pComponent->get_id() == component_id ) {
442 bComponentFound = true;
443 pComponent->save_to( &components_node );
444 }
445 }
446 } else {
447 WARNINGLOG( "Drumkit has no components. Storing an empty one as fallback." );
448 }
449
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 ) );
454 }
455 auto pDrumkitComponent = std::make_shared<DrumkitComponent>( 0, "Main" );
456 pDrumkitComponent->save_to( &components_node );
457 }
458 }
459 } else {
460 // Legacy export
461 if ( component_id == -1 ) {
462 ERRORLOG( "Exporting the full drumkit with all components is allowed when targeting the legacy versions >= 0.9.6" );
463 return;
464 }
465 }
466
467 if ( __instruments != nullptr && __instruments->size() > 0 ) {
468 __instruments->save_to( node, component_id, bRecentVersion, false );
469 } else {
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 );
475 }
476}
477
478bool Drumkit::save_samples( const QString& sDrumkitFolder, bool bSilent ) const
479{
480 if ( ! bSilent ) {
481 INFOLOG( QString( "Saving drumkit [%1] samples into [%2]" )
482 .arg( __name ).arg( sDrumkitFolder ) );
483 }
484
485 auto pInstrList = get_instruments();
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 ) {
490 continue;
491 }
492 for ( int n = 0; n < InstrumentComponent::getMaxLayers(); n++ ) {
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();
497
498 if ( src != dst ) {
499 QString original_dst = dst;
500
501 // If the destination path does not have an extension and there is a dot in the path, hell will break loose. QFileInfo maybe?
502 int insertPosition = original_dst.length();
503 if ( original_dst.lastIndexOf(".") > 0 ) {
504 insertPosition = original_dst.lastIndexOf(".");
505 }
506
507 pLayer->get_sample()->set_filename( dst );
508
509 if( ! Filesystem::file_copy( src, dst, bSilent ) ) {
510 return false;
511 }
512 }
513 }
514 }
515 }
516 }
517
518 return true;
519}
520
521bool Drumkit::save_image( const QString& sDrumkitDir, bool bSilent ) const
522{
523 if ( ! __image.isEmpty() && sDrumkitDir != __path ) {
524 QString src = __path + "/" + __image;
525 QString dst = sDrumkitDir + "/" + __image;
526 if ( Filesystem::file_exists( src, bSilent ) ) {
527 if ( ! Filesystem::file_copy( src, dst, bSilent ) ) {
528 ERRORLOG( QString( "Error copying %1 to %2").arg( src ).arg( dst ) );
529 return false;
530 }
531 }
532 }
533 return true;
534}
535
536std::shared_ptr<DrumkitComponent> Drumkit::getComponent( int nID ) const
537{
538 for ( const auto& pComponent : *__components ) {
539 if ( pComponent->get_id() == nID ) {
540 return pComponent;
541 }
542 }
543
544 return nullptr;
545}
546
548 int nNewId = __components->size();
549 for ( int ii = 0; ii < __components->size(); ++ii ) {
550 bool bIsPresent = false;
551 for ( const auto& ppComp : *__components ) {
552 if ( ppComp != nullptr && ppComp->get_id() == ii ) {
553 bIsPresent = true;
554 break;
555 }
556 }
557
558 if ( ! bIsPresent ){
559 nNewId = ii;
560 break;
561 }
562 }
563
564 return nNewId;
565}
566
567void Drumkit::addComponent( std::shared_ptr<DrumkitComponent> pComponent ) {
568 // Sanity check
569 if ( pComponent == nullptr ) {
570 ERRORLOG( "Invalid component" );
571 return;
572 }
573
574 for ( const auto& ppComponent : *__components ) {
575 if ( ppComponent == pComponent ) {
576 ERRORLOG( "Component is already present" );
577 return;
578 }
579 }
580
581 __components->push_back( pComponent );
582
583 for ( auto& ppInstrument : *__instruments ) {
584 ppInstrument->get_components()->push_back(
585 std::make_shared<InstrumentComponent>(pComponent->get_id()) );
586 }
587}
588
589void Drumkit::addInstrument( std::shared_ptr<Instrument> pInstrument ) {
590 if ( pInstrument == nullptr ) {
591 ERRORLOG( "invalid instrument" );
592 return;
593 }
594
595 // Ensure instrument components are contained in the Drumkit (compared by
596 // name). If not, add them.
597 for ( const auto& ppInstrumentComponent : *pInstrument->get_components() ) {
598 if ( ppInstrumentComponent == nullptr ) {
599 continue;
600 }
601
602 const int nComponentId = ppInstrumentComponent->get_drumkit_componentID();
603 if ( getComponent( nComponentId ) == nullptr ) {
604 ERRORLOG( QString( "No component of id [%1] found! Creating a new one" )
605 .arg( nComponentId ) );
606
607 addComponent( std::make_shared<DrumkitComponent>(
608 nComponentId, QString::number( nComponentId ) ) );
609 }
610 }
611
612 // Add components of this drumkit not already present in the instrument.
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() ) {
620 bIsPresent = true;
621 break;
622 }
623 }
624
625 if ( ! bIsPresent ){
626 auto pNewInstrCompo = std::make_shared<InstrumentComponent>(
627 ppThisKitsComponent->get_id() );
628 pInstrument->get_components()->push_back( pNewInstrCompo );
629 }
630 }
631 }
632
633 // In case there already is an instrument featuring its ID, we have to
634 // create a new one.
635 bool bIdAlreadyPresent = false;
636 for ( const auto& ppInstrument : *__instruments ) {
637 if ( ppInstrument != nullptr &&
638 ppInstrument->get_id() == pInstrument->get_id() ) {
639 bIdAlreadyPresent = true;
640 break;
641 }
642 }
643 if ( bIdAlreadyPresent && pInstrument->get_id() >= 0 ) {
644 int nNewId = __instruments->size();
645 for ( int ii = 0; ii < __instruments->size(); ++ii ) {
646 bool bIsPresent = false;
647 for ( const auto& ppInstrument : *__instruments ) {
648 if ( ppInstrument != nullptr &&
649 ppInstrument->get_id() == ii ) {
650 bIsPresent = true;
651 break;
652 }
653 }
654
655 if ( ! bIsPresent ) {
656 nNewId = ii;
657 break;
658 }
659 }
660
661 pInstrument->set_id( nNewId );
662 }
663
664 __instruments->add( pInstrument );
665}
666
667void Drumkit::set_instruments( std::shared_ptr<InstrumentList> instruments )
668{
669 __instruments = instruments;
670}
671
672void Drumkit::set_components( std::shared_ptr<std::vector<std::shared_ptr<DrumkitComponent>>> components )
673{
674 __components = components;
675}
676
678
679 for ( const auto& ppInstrument : *__instruments ) {
680 if ( ppInstrument != nullptr ) {
681
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 ) {
690 pSample->setLicense( get_license() );
691 }
692 }
693 }
694 }
695 }
696 }
697 }
698}
699
700std::vector<std::shared_ptr<InstrumentList::Content>> Drumkit::summarizeContent() const {
701 return __instruments->summarizeContent( __components );
702}
703
704bool Drumkit::remove( const QString& sDrumkitDir )
705{
706 if( ! Filesystem::drumkit_valid( sDrumkitDir ) ) {
707 ERRORLOG( QString( "%1 is not valid drumkit folder" ).arg( sDrumkitDir ) );
708 return false;
709 }
710
711 INFOLOG( QString( "Removing drumkit: %1" ).arg( sDrumkitDir ) );
712 if ( ! Filesystem::rm( sDrumkitDir, true ) ) {
713 ERRORLOG( QString( "Unable to remove drumkit: %1" ).arg( sDrumkitDir ) );
714 return false;
715 }
716
718 return true;
719}
720
721bool Drumkit::install( const QString& sSourcePath, const QString& sTargetPath,
722 QString* pInstalledPath, bool* pEncodingIssuesDetected,
723 bool bSilent )
724{
725 // Ensure variables are always set/initialized.
726 if ( pInstalledPath != nullptr ) {
727 *pInstalledPath = "";
728 }
729 if ( pEncodingIssuesDetected != nullptr ) {
730 *pEncodingIssuesDetected = false;
731 }
732
733 if ( sTargetPath.isEmpty() ) {
734 if ( ! bSilent ) {
735 INFOLOG( QString( "Install drumkit [%1]" ).arg( sSourcePath ) );
736 }
737
738 } else {
739 if ( ! Filesystem::path_usable( sTargetPath, true, false ) ) {
740 return false;
741 }
742
743 if ( ! bSilent ) {
744 INFOLOG( QString( "Extract drumkit from [%1] to [%2]" )
745 .arg( sSourcePath ).arg( sTargetPath ) );
746 }
747 }
748
749#ifdef H2CORE_HAVE_LIBARCHIVE
750 int nRet;
751
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;
756 }
757
758 struct archive* a;
759 struct archive_entry* entry;
760
761 if ( ! bSilent ) {
762 INFOLOG( QString( "Importing using `libarchive` version [%1]" )
763 .arg( ARCHIVE_VERSION_STRING ) );
764 }
765
766 a = archive_read_new();
767 if ( a == nullptr ) {
768 ERRORLOG( "Unable to create new archive" );
769 return false;
770 }
771
772#if ARCHIVE_VERSION_NUMBER < 3000000
773 archive_read_support_compression_all( a );
774#else
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 ) ) );
779 }
780#endif
781
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 ) ) );
786 }
787
788 // Shutdown version used on error. Therefore, contained commands are not
789 // checked for errors themselves.
790 auto tearDown = [&]() {
791 archive_read_close( a );
792
793#if ARCHIVE_VERSION_NUMBER < 3000000
794 archive_read_finish( a );
795#else
796 archive_read_free( a );
797#endif
798 };
799
800#if ARCHIVE_VERSION_NUMBER < 3000000
801 const auto sSourcePathUtf8 = sSourcePath.toUtf8();
802 nRet = archive_read_open_file( a, sSourcePathUtf8.constData(), 10240 );
803#else
804 #ifdef WIN32
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 );
809 #else
810 const auto sSourcePathUtf8 = sSourcePath.toUtf8();
811 nRet = archive_read_open_filename( a, sSourcePathUtf8.constData(),
812 10240 );
813 #endif
814#endif
815 if ( nRet != ARCHIVE_OK ) {
816 ERRORLOG( QString( "Unable to open archive [%1] for reading: %2" )
817 .arg( sSourcePath )
818 .arg( archive_error_string( a ) ) );
819 tearDown();
820 return false;
821 }
822
823 QString sDrumkitDir;
824 if ( ! sTargetPath.isEmpty() ) {
825 sDrumkitDir = sTargetPath + "/";
826 } else {
827 sDrumkitDir = Filesystem::usr_drumkits_dir() + "/";
828 }
829
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 ) ) );
834 tearDown();
835 return false;
836 }
837 if ( entry == nullptr ) {
838 ERRORLOG( "Couldn't read in next archive entry" );
839 return false;
840 }
841
842 QString sNewPath = QString::fromUtf8( archive_entry_pathname_utf8( entry ) );
843 if ( sNewPath.isEmpty() ) {
844 sNewPath = QString( archive_entry_pathname( entry ) );
845 }
846
847 if ( ! bUseUtf8Encoding ) {
848 // In case `libarchive` is not able to support UTF-8 on the system,
849 // we remove (a lot of) characters. Else they will be represented by
850 // wacky ones and the calling routine would have no idea where the
851 // resulting kit did end up.
852 const auto sNewPathTrimmed = Filesystem::removeUtf8Characters( sNewPath );
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;
858 }
859 sNewPath = sNewPathTrimmed;
860 }
861 }
862 sNewPath.prepend( sDrumkitDir );
863
864 if ( pInstalledPath != nullptr &&
865 sNewPath.contains( Filesystem::drumkit_xml() ) ) {
866 // This file must be part of every kit and allows us to set this
867 // variable only once.
868 QFileInfo installInfo( sNewPath );
869 *pInstalledPath = installInfo.absoluteDir().absolutePath();
870 }
871 QByteArray newpath = sNewPath.toUtf8();
872
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" )
877 .arg( sNewPath )
878 .arg( archive_error_string( a ) ) );
879 }
880 else if ( nRet != ARCHIVE_OK ) {
881 ERRORLOG( QString( "Unable to extract content of [%1] from archive: %2" )
882 .arg( sNewPath )
883 .arg( archive_error_string( a ) ) );
884 tearDown();
885 return false;
886 }
887 }
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 ) ) );
892 return false;
893 }
894
895#if ARCHIVE_VERSION_NUMBER < 3000000
896 archive_read_finish( a );
897#else
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 ) ) );
902 }
903#endif
904
905 return true;
906#else // H2CORE_HAVE_LIBARCHIVE
907#ifndef WIN32
908 // GUNZIP
909
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" );
913 if ( !gzip_file ) {
914 _ERRORLOG( QString( "Error reading drumkit file: %1" )
915 .arg( sSourcePath ) );
916 gzclose( gzip_file );
917 fclose( gzd_file );
918 return false;
919 }
920 uchar buf[4096];
921 while ( gzread( gzip_file, buf, 4096 ) > 0 ) {
922 fwrite( buf, sizeof( uchar ), 4096, gzd_file );
923 }
924 gzclose( gzip_file );
925 fclose( gzd_file );
926 // UNTAR
927 TAR* tar_file;
928
929 QByteArray tar_path = gzd_name.toLocal8Bit();
930
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 ) ) ) );
933 return false;
934 }
935 bool ret = true;
936 char dst_dir[1024];
937
938 QString dk_dir;
939 if ( ! sTargetPath.isEmpty() ) {
940 dk_dir = sTargetPath + "/";
941 } else {
942 dk_dir = Filesystem::usr_drumkits_dir() + "/";
943 }
944
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 ) ) ) );
949 ret = false;
950 }
951 if ( tar_close( tar_file ) != 0 ) {
952 _ERRORLOG( QString( "tar_close(): %1" )
953 .arg( QString::fromLocal8Bit( strerror( errno ) ) ) );
954 ret = false;
955 }
956 return ret;
957#else // WIN32
958 _ERRORLOG( "WIN32 NOT IMPLEMENTED" );
959 return false;
960#endif
961#endif
962}
963
964bool Drumkit::exportTo( const QString& sTargetDir, const QString& sComponentName,
965 bool bRecentVersion, bool* pUtf8Encoded, bool bSilent ) {
966 if ( pUtf8Encoded != nullptr ) {
967 // Ensure the variable is always set/initialized.
968 *pUtf8Encoded = false;
969 }
970
971 if ( ! Filesystem::path_usable( sTargetDir, true, false ) ) {
972 ERRORLOG( QString( "Provided destination folder [%1] is not valid" )
973 .arg( sTargetDir ) );
974 return false;
975 }
976
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" );
979 return false;
980 }
981
982 // When performing an export of a single component, the resulting
983 // file will be <DRUMKIT_NAME>_<COMPONENT_NAME>.h2drumkit. This
984 // itself is nice because the user can not choose the name of the
985 // resulting file and it would not be possible to store the export
986 // of multiple components in a single folder otherwise. But if all
987 // those different .h2drumkit would be extracted into the same
988 // folder there would be easily confusion or maybe even loss of
989 // data. We thus temporary rename the drumkit within this
990 // function.
991 // If a legacy export is asked for (!bRecentVersion) the suffix
992 // "_legacy" will be appended as well in order to provide unique
993 // filenames for all export options of a drumkit that can be
994 // selected in the GUI.
995 QString sOldDrumkitName = __name;
996 QString sDrumkitName = getExportName( sComponentName, bRecentVersion );
997
998 QString sTargetName = sTargetDir + "/" + sDrumkitName +
1000
1001 if ( ! bSilent ) {
1002 QString sMsg( "Export ");
1003
1004 if ( sComponentName.isEmpty() && bRecentVersion ) {
1005 sMsg.append( "drumkit " );
1006 } else {
1007 sMsg.append( QString( "component: [%1] " ).arg( sComponentName ) );
1008 }
1009
1010 sMsg.append( QString( "to [%1] " ).arg( sTargetName ) );
1011
1012 if ( bRecentVersion ) {
1013 sMsg.append( "using the most recent format" );
1014 } else {
1015 sMsg.append( "using the legacy format supported by Hydrogen versions <= 0.9.6" );
1016 }
1017
1018 INFOLOG( sMsg );
1019 }
1020
1021 // Unique temporary folder to save intermediate drumkit.xml and
1022 // component files. The uniqueness is required in case several
1023 // threads or instances of Hydrogen do export a drumkit at once.
1024 QTemporaryDir tmpFolder( Filesystem::tmp_dir() + "/XXXXXX" );
1025 if ( ! sComponentName.isEmpty() ) {
1026 tmpFolder.setAutoRemove( false );
1027 }
1028
1029 // In case we just export a single component, we store a pruned
1030 // version of the drumkit with all other DrumkitComponents removed
1031 // from the Instruments in a temporary folder and use this one as
1032 // a basis for further compression.
1033 int nComponentID = -1;
1034 if ( ! sComponentName.isEmpty() ) {
1035 for ( auto pComponent : *__components ) {
1036 if( pComponent->get_name().compare( sComponentName ) == 0) {
1037 nComponentID = pComponent->get_id();
1038 set_name( sDrumkitName );
1039 break;
1040 }
1041 }
1042 if ( nComponentID == -1 ) {
1043 ERRORLOG( QString( "Component [%1] could not be found in current Drumkit [%2]" )
1044 .arg( sComponentName )
1045 .arg( toQString( "", true ) ) );
1046 set_name( sOldDrumkitName );
1047 return false;
1048 }
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 ) );
1052 }
1053 }
1054
1055 if ( ! Filesystem::dir_readable( __path, true ) ) {
1056 ERRORLOG( QString( "Unabled to access folder associated with drumkit [%1]" )
1057 .arg( __path ) );
1058 set_name( sOldDrumkitName );
1059 return false;
1060 }
1061
1062 QDir sourceDir( __path );
1063
1064 QStringList sourceFilesList = sourceDir.entryList( QDir::Files );
1065 // In case just a single component is exported, we only add
1066 // samples associated with it to the .h2drumkit file.
1067 QStringList filesUsed;
1068
1069 // List of formats libsndfile is able to import (see
1070 // https://libsndfile.github.io/libsndfile/api.html#open).
1071 // This list is used to decide what will happen to a file on a
1072 // single-component export in case the file is not associated with
1073 // a sample of an instrument belonging to the exported
1074 // component. If its suffix is contained in this list, the file is
1075 // considered to be part of an instrument we like to drop. If not,
1076 // it might be a metafile, like LICENSE, README, or the kit's
1077 // image.
1078 // The list does not have to be comprehensive as a "leakage" of
1079 // audio files in the resulting .h2drumkit is not a big problem.
1080 QStringList suffixBlacklist;
1081 for ( const auto& fformat : Filesystem::supportedAudioFormats() ) {
1082 suffixBlacklist << Filesystem::AudioFormatToSuffix( fformat );
1083 }
1084
1085 bool bSampleFound;
1086
1087 for ( const auto& ssFile : sourceFilesList ) {
1088 if( ssFile.compare( Filesystem::drumkit_xml() ) == 0 &&
1089 nComponentID != -1 ) {
1090 filesUsed << Filesystem::drumkit_file( tmpFolder.path() );
1091 } else {
1092
1093 bSampleFound = false;
1094 for( const auto& pInstr : *( get_instruments() ) ){
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 ) ) {
1100 for( int n = 0; n < InstrumentComponent::getMaxLayers(); n++ ) {
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;
1106 break;
1107 }
1108 }
1109 }
1110 }
1111 }
1112 }
1113 }
1114
1115 // Should we drop the file?
1116 if ( ! bSampleFound ) {
1117 QFileInfo ffileInfo( sourceDir.filePath( ssFile ) );
1118 if ( ! suffixBlacklist.contains( ffileInfo.suffix(),
1119 Qt::CaseInsensitive ) ) {
1120
1121 // We do not want to export any old backups
1122 // created during the upgrade process of the
1123 // drumkits.
1124 if ( ! ( ssFile.contains( Filesystem::drumkit_xml() ) &&
1125 ssFile.contains( ".bak" ) ) ) {
1126 filesUsed << sourceDir.filePath( ssFile );
1127 }
1128 }
1129 }
1130 }
1131 }
1132
1133#if defined(H2CORE_HAVE_LIBARCHIVE)
1134
1135 if ( ! bSilent ) {
1136 INFOLOG( QString( "Exporting using `libarchive` version [%1]" )
1137 .arg( ARCHIVE_VERSION_STRING ) );
1138 }
1139
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;
1144 }
1145
1146 struct archive *a;
1147 struct archive_entry *entry;
1148 struct stat st;
1149 const int nBufferSize = 8192;
1150 char buff[ nBufferSize ];
1151 int nBytesRead, nRet;
1152
1153 // Write it back for the calling routine.
1154 if ( pUtf8Encoded != nullptr ) {
1155 *pUtf8Encoded = bUseUtf8Encoding;
1156 }
1157
1158 a = archive_write_new();
1159 if ( a == nullptr ) {
1160 ERRORLOG( "Unable to create new archive" );
1161 set_name( sOldDrumkitName );
1162 return false;
1163 }
1164
1165#if ARCHIVE_VERSION_NUMBER < 3000000
1166 archive_write_set_compression_gzip( a );
1167#else
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 ) ) );
1172 set_name( sOldDrumkitName );
1173 return false;
1174 }
1175#endif
1176
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 ) ) );
1181 set_name( sOldDrumkitName );
1182 return false;
1183 }
1184
1185
1186#ifdef WIN32
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() );
1191#else
1192 const auto sTargetNameUtf8 = sTargetName.toUtf8();
1193 const auto targetPath = sTargetNameUtf8.constData();
1194 nRet = archive_write_open_filename( a, targetPath );
1195#endif
1196 if ( nRet != ARCHIVE_OK ) {
1197 ERRORLOG( QString("Couldn't create archive [%1]: %2" )
1198 .arg( targetPath )
1199 .arg( archive_error_string( a ) ) );
1200 set_name( sOldDrumkitName );
1201 return false;
1202 }
1203
1204 bool bFoundFileInRightComponent;
1205 for ( const auto& sFilename : filesUsed ) {
1206 QFileInfo ffileInfo( sFilename );
1207 QString sTargetFilename = sDrumkitName + "/" + ffileInfo.fileName();
1208
1209 // Small sanity check since the libarchive code won't fail
1210 // gracefully but segfaults if the provided file does not
1211 // exist.
1212 if ( ! Filesystem::file_readable( sFilename, true ) ) {
1213 ERRORLOG( QString( "Unable to export drumkit. File [%1] does not exists or is not readable." )
1214 .arg( sFilename ) );
1215 set_name( sOldDrumkitName );
1216 return false;
1217 }
1218
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" );
1224 set_name( sOldDrumkitName );
1225 return false;
1226 }
1227
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());
1233 } else {
1234#else
1235 {
1236#endif
1237 archive_entry_set_pathname(entry, sTargetFilenameUtf8.constData());
1238 }
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" )
1245 .arg( sFilename )
1246 .arg( archive_error_string( a ) ) );
1247 set_name( sOldDrumkitName );
1248 return false;
1249 }
1250
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 );
1256 continue;
1257 }
1258
1259 QDataStream stream( &file );
1260 nBytesRead = stream.readRawData( buff, nBufferSize );
1261 while ( nBytesRead > 0 ) {
1262 nRet = archive_write_data( a, buff, nBytesRead );
1263 if ( nRet < 0 ) {
1264 ERRORLOG( QString( "Error while writing data to entry of [%1]: %2" )
1265 .arg( sFilename ).arg( archive_error_string( a ) ) );
1266 break;
1267 }
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 ) );
1271 }
1272
1273 nBytesRead = stream.readRawData( buff, nBufferSize );
1274 }
1275 file.close();
1276 archive_entry_free(entry);
1277 }
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 ) ) );
1282 set_name( sOldDrumkitName );
1283 return false;
1284 }
1285
1286
1287#if ARCHIVE_VERSION_NUMBER < 3000000
1288 archive_write_finish(a);
1289#else
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 ) ) );
1294 }
1295#endif
1296
1297 sourceFilesList.clear();
1298
1299 // Only clean up the temp folder when everything was
1300 // working. Else, it's probably worth inspecting its content (and
1301 // the system will clean it up anyway).
1302 Filesystem::rm( tmpFolder.path(), true, true );
1303
1304 set_name( sOldDrumkitName );
1305
1306 return true;
1307#else // No LIBARCHIVE
1308
1309#ifndef WIN32
1310 if ( nComponentID != -1 ) {
1311 // In order to add components name to the folder name we have
1312 // to copy _all_ files to a temporary folder holding the same
1313 // name. This is unarguably a quite expensive operation. But
1314 // exporting is only down sparsely and almost all versions of
1315 // Hydrogen should come with libarchive support anyway. On the
1316 // other hand, being consistent and prevent confusion and loss
1317 // of data beats sparsely excessive copying.
1318 QString sDirName = getFolderName();
1319
1320 QDir sTmpSourceDir( tmpFolder.path() + "/" + sDirName );
1321 if ( sTmpSourceDir.exists() ) {
1322 sTmpSourceDir.removeRecursively();
1323 }
1324 if ( ! Filesystem::path_usable( tmpFolder.path() + "/" + sDirName,
1325 true, true ) ) {
1326 ERRORLOG( QString( "Unable to create tmp folder [%1]" )
1327 .arg( tmpFolder.path() + "/" + sDirName ) );
1328 set_name( sOldDrumkitName );
1329 return false;
1330 }
1331
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 );
1338 if ( ! Filesystem::file_copy( ssFile, sNewFilePath, true, true ) ) {
1339 ERRORLOG( QString( "Unable to copy file [%1] to [%2]." )
1340 .arg( ssFile ).arg( sNewFilePath ) );
1341 set_name( sOldDrumkitName );
1342 return false;
1343 }
1344
1345 copiedFiles << sNewFilePath;
1346 }
1347
1348 filesUsed = copiedFiles;
1349 sourceDir = QDir( tmpFolder.path() + "/" + sDirName );
1350 }
1351
1352 // Since there is no way to alter the target names of the files
1353 // provided to command line `tar` and we want the output to be
1354 // identically to the only created used libarchive, we need to do
1355 // some string replacement in here. If not, the unpack to
1356 // ./home/USER_NAME_RUNNING_THE_EXPORT/.hydrogen/data/drumkits/DRUMKIT_NAME/
1357 // but we instead want it to unpack to ./DRUMKIT_NAME/
1358 filesUsed = filesUsed.replaceInStrings( sourceDir.absolutePath(),
1359 sourceDir.dirName() );
1360
1361 QString sCmd = QString( "tar czf %1 -C %2 -- \"%3\"" )
1362 .arg( sTargetName )
1363 .arg( sourceDir.absolutePath().left( sourceDir.absolutePath().lastIndexOf( "/" ) ) )
1364 .arg( filesUsed.join( "\" \"" ) );
1365 int nRet = std::system( sCmd.toLocal8Bit() );
1366
1367 if ( nRet != 0 ) {
1368 ERRORLOG( QString( "Unable to export drumkit using system command:\n%1" )
1369 .arg( sCmd ) );
1370 set_name( sOldDrumkitName );
1371 return false;
1372 }
1373
1374 // Only clean up the temp folder when everything was
1375 // working. Else, it's probably worth inspecting its content (and
1376 // the system will clean it up anyway).
1377 Filesystem::rm( tmpFolder.path(), true, true );
1378
1379 set_name( sOldDrumkitName );
1380
1381 return true;
1382#else // WIN32
1383 ERRORLOG( "Operation not supported on Windows" );
1384
1385 return false;
1386#endif
1387#endif // LIBARCHIVE
1388
1389}
1390
1391QString Drumkit::toQString( const QString& sPrefix, bool bShort ) const {
1392 QString s = Base::sPrintIndention;
1393 QString sOutput;
1394 if ( ! bShort ) {
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 ) );
1406 for ( auto cc : *__components ) {
1407 if ( cc != nullptr ) {
1408 sOutput.append( QString( "%1" ).arg( cc->toQString( sPrefix + s + s, bShort ) ) );
1409 }
1410 }
1411 } else {
1412
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() ) )
1421 .append( QString( ", samples_loaded: %1" ).arg( __samples_loaded ) )
1422 .append( QString( ", [%1]" ).arg( __instruments->toQString( sPrefix + s, bShort ) ) )
1423 .append( QString( ", components: [ " ) );
1424 for ( auto cc : *__components ) {
1425 if ( cc != nullptr ) {
1426 sOutput.append( QString( "[%1]" ).arg( cc->toQString( sPrefix + s + s, bShort ).replace( "\n", " " ) ) );
1427 }
1428 }
1429 sOutput.append( "]\n" );
1430 }
1431
1432 return sOutput;
1433}
1434
1435};
1436
1437/* vim: set softtabstop=4 noexpandtab: */
#define INFOLOG(x)
Definition Object.h:240
#define WARNINGLOG(x)
Definition Object.h:241
#define ERRORLOG(x)
Definition Object.h:242
#define _ERRORLOG(x)
Definition Object.h:248
static QString sPrintIndention
String used to format the debugging string output of some core classes.
Definition Object.h:127
static std::shared_ptr< DrumkitComponent > load_from(XMLNode *node, bool *pLegacyFormatEncountered=nullptr)
QString __name
drumkit name
Definition Drumkit.h:274
int findUnusedComponentId() const
Definition Drumkit.cpp:547
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.
Definition Drumkit.cpp:265
const QString & get_info() const
__info accessor
Definition Drumkit.h:380
std::shared_ptr< InstrumentList > get_instruments() const
returns __instruments
Definition Drumkit.h:338
void set_name(const QString &name)
__name setter
Definition Drumkit.h:353
const License & get_license() const
__license accessor
Definition Drumkit.h:390
const QString & get_name() const
__name accessor
Definition Drumkit.h:358
void save_to(XMLNode *node, int component_id=-1, bool bRecentVersion=true, bool bSilent=false) const
Definition Drumkit.cpp:415
static bool install(const QString &sSourcePath, const QString &sTargetPath="", QString *pInstalledPath=nullptr, bool *pEncodingIssuesDetected=nullptr, bool bSilent=false)
Extract a .h2drumkit file.
Definition Drumkit.cpp:721
bool exportTo(const QString &sTargetDir, const QString &sComponentName="", bool bRecentVersion=true, bool *pUtf8Encoded=nullptr, bool bSilent=false)
Compresses the drumkit into a .h2drumkit file.
Definition Drumkit.cpp:964
bool save_image(const QString &dk_dir, bool bSilent=false) const
save the drumkit image into the new directory
Definition Drumkit.cpp:521
std::shared_ptr< std::vector< std::shared_ptr< DrumkitComponent > > > __components
list of drumkit component
Definition Drumkit.h:283
const QString & get_image() const
__image accessor
Definition Drumkit.h:400
void propagateLicense()
Assign the license stored in #m_license to all samples contained in the kit.
Definition Drumkit.cpp:677
License __imageLicense
drumkit image license
Definition Drumkit.h:279
~Drumkit()
drumkit destructor, delete __instruments
Definition Drumkit.cpp:90
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.
Definition Drumkit.cpp:290
std::shared_ptr< InstrumentList > __instruments
the list of instruments
Definition Drumkit.h:282
QString __info
drumkit free text
Definition Drumkit.h:276
bool save_samples(const QString &dk_dir, bool bSilent=false) const
save a drumkit instruments samples into a directory
Definition Drumkit.cpp:478
void load_samples()
Calls the InstrumentList::load_samples() member function of __instruments.
Definition Drumkit.cpp:234
QString __author
drumkit author
Definition Drumkit.h:275
static bool remove(const QString &sDrumkitDir)
remove a drumkit from the disk
Definition Drumkit.cpp:704
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.
Definition Drumkit.cpp:94
const QString & get_author() const
__author accessor
Definition Drumkit.h:370
std::shared_ptr< DrumkitComponent > getComponent(int nID) const
Definition Drumkit.cpp:536
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...
Definition Drumkit.cpp:700
QString __image
drumkit image filename
Definition Drumkit.h:278
void addComponent(std::shared_ptr< DrumkitComponent > pComponent)
Definition Drumkit.cpp:567
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
Definition Drumkit.cpp:153
static License loadLicenseFrom(const QString &sDrumkitDir, bool bSilent=false)
Loads the license information of a drumkit contained in directory sDrumkitDir.
Definition Drumkit.cpp:243
QString getExportName(const QString &sComponentName, bool bRecentVersion) const
Returns the base name used when exporting the drumkit.
Definition Drumkit.cpp:330
QString toQString(const QString &sPrefix="", bool bShort=true) const override
Formatted string version for debugging purposes.
Definition Drumkit.cpp:1391
const bool samples_loaded() const
return true if the samples are loaded
Definition Drumkit.h:415
void addInstrument(std::shared_ptr< Instrument > pInstrument)
Adds an instrument and takes care of registering DrumkitComponents missing for contained InstrumentCo...
Definition Drumkit.cpp:589
QString __path
absolute drumkit path
Definition Drumkit.h:273
void unload_samples()
Calls the InstrumentList::unload_samples() member function of __instruments.
Definition Drumkit.cpp:317
const QString & get_path() const
__path accessor
Definition Drumkit.h:348
Drumkit()
drumkit constructor, does nothing
Definition Drumkit.cpp:57
bool __samples_loaded
true if the instrument samples are loaded
Definition Drumkit.h:281
bool save(const QString &sDrumkitPath="", int nComponentID=-1, bool bRecentVersion=true, bool bSilent=false)
Save a drumkit to disk.
Definition Drumkit.cpp:343
QString getFolderName() const
Returns a version of __name stripped of all whitespaces and other characters which would prevent its ...
Definition Drumkit.cpp:326
void set_components(std::shared_ptr< std::vector< std::shared_ptr< DrumkitComponent > > > components)
Definition Drumkit.cpp:672
void set_instruments(std::shared_ptr< InstrumentList > instruments)
set __instruments, delete existing one
Definition Drumkit.cpp:667
License __license
drumkit license description
Definition Drumkit.h:277
const License & get_image_license() const
__imageLicense accessor
Definition Drumkit.h:410
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
Definition Filesystem.h:121
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)
Definition Future.cpp:33
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
SoundLibraryDatabase * getSoundLibraryDatabase() const
Definition Hydrogen.h:95
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.
Definition License.h:48
@ GPL
Not a desirable license for audio data but introduced here specifically since it is already used by a...
Definition License.h:68
static QString getGPLLicenseNotice(const QString &sAuthor)
Definition License.h:194
void updateDrumkits(bool bTriggerEvent=true)
XMLDoc is a subclass of QDomDocument with read and write methods.
Definition Xml.h:182
XMLNode set_root(const QString &node_name, const QString &xmlns=nullptr)
create the xml header and root node
Definition Xml.cpp:336
bool read(const QString &filepath, bool bSilent=false)
read the content of an xml file
Definition Xml.cpp:277
bool write(const QString &filepath)
write itself into a file
Definition Xml.cpp:311
XMLNode is a subclass of QDomNode with read and write values methods.
Definition Xml.h:39
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
Definition Xml.cpp:76
XMLNode createNode(const QString &name)
create a new XMLNode that has to be appended into de XMLDoc
Definition Xml.cpp:44
void write_string(const QString &node, const QString &value)
write a string into a child node
Definition Xml.cpp:250