File indexing completed on 2025-01-19 04:24:37
0001 /**************************************************************************************** 0002 * Copyright (c) 2011 Bart Cerneels <bart.cerneels@kde.org> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify it under * 0005 * the terms of the GNU General Public License as published by the Free Software * 0006 * Foundation; either version 2 of the License, or (at your option) any later * 0007 * version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0012 * * 0013 * You should have received a copy of the GNU General Public License along with * 0014 * this program. If not, see <http://www.gnu.org/licenses/>. * 0015 ****************************************************************************************/ 0016 0017 #define DEBUG_PREFIX "UmsCollection" 0018 0019 #include "UmsCollection.h" 0020 0021 #include "amarokconfig.h" 0022 #include "ui_UmsConfiguration.h" 0023 #include "collectionscanner/Track.h" 0024 #include "core/capabilities/ActionsCapability.h" 0025 #include "core/logger/Logger.h" 0026 #include "core/meta/Meta.h" 0027 #include "core/support/Components.h" 0028 #include "core/support/Debug.h" 0029 #include "core-impl/collections/support/MemoryQueryMaker.h" 0030 #include "core-impl/collections/support/MemoryMeta.h" 0031 #include "core-impl/collections/umscollection/UmsCollectionLocation.h" 0032 #include "core-impl/collections/umscollection/UmsTranscodeCapability.h" 0033 #include "core-impl/meta/file/File.h" 0034 #include "dialogs/OrganizeCollectionDialog.h" 0035 #include "dialogs/TrackOrganizer.h" //TODO: move to core/utils 0036 #include "scanner/GenericScanManager.h" 0037 0038 #include <Solid/DeviceInterface> 0039 #include <Solid/DeviceNotifier> 0040 #include <Solid/GenericInterface> 0041 #include <Solid/OpticalDisc> 0042 #include <Solid/PortableMediaPlayer> 0043 #include <Solid/StorageAccess> 0044 #include <Solid/StorageDrive> 0045 #include <Solid/StorageVolume> 0046 0047 #include <QThread> 0048 #include <QTimer> 0049 #include <QUrl> 0050 0051 #include <KConfigGroup> 0052 #include <KDiskFreeSpaceInfo> 0053 0054 0055 UmsCollectionFactory::UmsCollectionFactory() 0056 : CollectionFactory() 0057 {} 0058 0059 UmsCollectionFactory::~UmsCollectionFactory() 0060 { 0061 } 0062 0063 void 0064 UmsCollectionFactory::init() 0065 { 0066 connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, 0067 this, &UmsCollectionFactory::slotAddSolidDevice ); 0068 connect( Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, 0069 this, &UmsCollectionFactory::slotRemoveSolidDevice ); 0070 0071 // detect UMS devices that were already connected on startup 0072 QString query( "IS StorageAccess" ); 0073 QList<Solid::Device> devices = Solid::Device::listFromQuery( query ); 0074 foreach( const Solid::Device &device, devices ) 0075 { 0076 if( identifySolidDevice( device.udi() ) ) 0077 createCollectionForSolidDevice( device.udi() ); 0078 } 0079 m_initialized = true; 0080 } 0081 0082 void 0083 UmsCollectionFactory::slotAddSolidDevice( const QString &udi ) 0084 { 0085 if( m_collectionMap.contains( udi ) ) 0086 return; // a device added twice (?) 0087 0088 if( identifySolidDevice( udi ) ) 0089 createCollectionForSolidDevice( udi ); 0090 } 0091 0092 void 0093 UmsCollectionFactory::slotAccessibilityChanged( bool accessible, const QString &udi ) 0094 { 0095 if( accessible ) 0096 slotAddSolidDevice( udi ); 0097 else 0098 slotRemoveSolidDevice( udi ); 0099 } 0100 0101 void 0102 UmsCollectionFactory::slotRemoveSolidDevice( const QString &udi ) 0103 { 0104 UmsCollection *collection = m_collectionMap.take( udi ); 0105 if( collection ) 0106 collection->slotDestroy(); 0107 } 0108 0109 void 0110 UmsCollectionFactory::slotRemoveAndTeardownSolidDevice( const QString &udi ) 0111 { 0112 UmsCollection *collection = m_collectionMap.take( udi ); 0113 if( collection ) 0114 collection->slotEject(); 0115 } 0116 0117 void 0118 UmsCollectionFactory::slotCollectionDestroyed( QObject *collection ) 0119 { 0120 // remove destroyed collection from m_collectionMap 0121 QMutableMapIterator<QString, UmsCollection *> it( m_collectionMap ); 0122 while( it.hasNext() ) 0123 { 0124 it.next(); 0125 if( (QObject *) it.value() == collection ) 0126 it.remove(); 0127 } 0128 } 0129 0130 bool 0131 UmsCollectionFactory::identifySolidDevice( const QString &udi ) const 0132 { 0133 Solid::Device device( udi ); 0134 if( !device.is<Solid::StorageAccess>() ) 0135 return false; 0136 // HACK to exclude iPods until UMS and iPod have common collection factory 0137 if( device.vendor().contains( "Apple", Qt::CaseInsensitive ) ) 0138 return false; 0139 0140 // everything okay, check whether the device is a data CD 0141 if( device.is<Solid::OpticalDisc>() ) 0142 { 0143 const Solid::OpticalDisc *disc = device.as<Solid::OpticalDisc>(); 0144 if( disc && ( disc->availableContent() & Solid::OpticalDisc::Data ) ) 0145 return true; 0146 return false; 0147 } 0148 0149 // check whether there is parent USB StorageDrive device 0150 while( device.isValid() ) 0151 { 0152 if( device.is<Solid::StorageDrive>() ) 0153 { 0154 Solid::StorageDrive *sd = device.as<Solid::StorageDrive>(); 0155 if( sd->driveType() == Solid::StorageDrive::CdromDrive ) 0156 return false; 0157 // USB Flash discs are usually hotpluggable, SD/MMC card slots are usually removable 0158 return sd->isHotpluggable() || sd->isRemovable(); 0159 } 0160 device = device.parent(); 0161 } 0162 return false; // no valid parent USB StorageDrive 0163 } 0164 0165 void 0166 UmsCollectionFactory::createCollectionForSolidDevice( const QString &udi ) 0167 { 0168 DEBUG_BLOCK 0169 Solid::Device device( udi ); 0170 Solid::StorageAccess *ssa = device.as<Solid::StorageAccess>(); 0171 if( !ssa ) 0172 { 0173 warning() << __PRETTY_FUNCTION__ << "called for non-StorageAccess device!?!"; 0174 return; 0175 } 0176 if( ssa->isIgnored() ) 0177 { 0178 debug() << "device" << udi << "ignored, ignoring :-)"; 0179 return; 0180 } 0181 0182 // we are definitely interested in this device, listen for accessibility changes 0183 disconnect( ssa, &Solid::StorageAccess::accessibilityChanged, this, nullptr ); 0184 connect( ssa, &Solid::StorageAccess::accessibilityChanged, 0185 this, &UmsCollectionFactory::slotAccessibilityChanged ); 0186 0187 if( !ssa->isAccessible() ) 0188 { 0189 debug() << "device" << udi << "not accessible, ignoring for now"; 0190 return; 0191 } 0192 0193 UmsCollection *collection = new UmsCollection( device ); 0194 m_collectionMap.insert( udi, collection ); 0195 0196 // when the collection is destroyed by someone else, remove it from m_collectionMap: 0197 connect( collection, &QObject::destroyed, this, &UmsCollectionFactory::slotCollectionDestroyed ); 0198 0199 // try to gracefully destroy collection when unmounting is requested using 0200 // external means: (Device notifier plasmoid etc.). Because the original action could 0201 // fail if we hold some files on the device open, we try to tearDown the device too. 0202 connect( ssa, &Solid::StorageAccess::teardownRequested, this, &UmsCollectionFactory::slotRemoveAndTeardownSolidDevice ); 0203 0204 Q_EMIT newCollection( collection ); 0205 } 0206 0207 //UmsCollection 0208 0209 QString UmsCollection::s_settingsFileName( ".is_audio_player" ); 0210 QString UmsCollection::s_musicFolderKey( "audio_folder" ); 0211 QString UmsCollection::s_musicFilenameSchemeKey( "music_filenamescheme" ); 0212 QString UmsCollection::s_vfatSafeKey( "vfat_safe" ); 0213 QString UmsCollection::s_asciiOnlyKey( "ascii_only" ); 0214 QString UmsCollection::s_postfixTheKey( "ignore_the" ); 0215 QString UmsCollection::s_replaceSpacesKey( "replace_spaces" ); 0216 QString UmsCollection::s_regexTextKey( "regex_text" ); 0217 QString UmsCollection::s_replaceTextKey( "replace_text" ); 0218 QString UmsCollection::s_podcastFolderKey( "podcast_folder" ); 0219 QString UmsCollection::s_autoConnectKey( "use_automatically" ); 0220 QString UmsCollection::s_collectionName( "collection_name" ); 0221 QString UmsCollection::s_transcodingGroup( "transcoding" ); 0222 0223 UmsCollection::UmsCollection( const Solid::Device &device ) 0224 : Collection() 0225 , m_device( device ) 0226 , m_mc( nullptr ) 0227 , m_tracksParsed( false ) 0228 , m_autoConnect( false ) 0229 , m_musicFilenameScheme( "%artist%/%album%/%track% %title%" ) 0230 , m_vfatSafe( true ) 0231 , m_asciiOnly( false ) 0232 , m_postfixThe( false ) 0233 , m_replaceSpaces( false ) 0234 , m_regexText( QString() ) 0235 , m_replaceText( QString() ) 0236 , m_collectionName( QString() ) 0237 , m_scanManager( nullptr ) 0238 , m_lastUpdated( 0 ) 0239 { 0240 debug() << "Creating UmsCollection for device with udi: " << m_device.udi(); 0241 0242 m_updateTimer.setSingleShot( true ); 0243 connect( this, &UmsCollection::startUpdateTimer, this, &UmsCollection::slotStartUpdateTimer ); 0244 connect( &m_updateTimer, &QTimer::timeout, this, &UmsCollection::collectionUpdated ); 0245 0246 m_configureAction = new QAction( QIcon::fromTheme( "configure" ), i18n( "&Configure Device" ), this ); 0247 m_configureAction->setProperty( "popupdropper_svg_id", "configure" ); 0248 connect( m_configureAction, &QAction::triggered, this, &UmsCollection::slotConfigure ); 0249 0250 m_parseAction = new QAction( QIcon::fromTheme( "checkbox" ), i18n( "&Activate This Collection" ), this ); 0251 m_parseAction->setProperty( "popupdropper_svg_id", "edit" ); 0252 connect( m_parseAction, &QAction::triggered, this, &UmsCollection::slotParseActionTriggered ); 0253 0254 m_ejectAction = new QAction( QIcon::fromTheme( "media-eject" ), i18n( "&Eject Device" ), 0255 const_cast<UmsCollection*>( this ) ); 0256 m_ejectAction->setProperty( "popupdropper_svg_id", "eject" ); 0257 connect( m_ejectAction, &QAction::triggered, this, &UmsCollection::slotEject ); 0258 0259 init(); 0260 } 0261 0262 UmsCollection::~UmsCollection() 0263 { 0264 DEBUG_BLOCK 0265 } 0266 0267 void 0268 UmsCollection::init() 0269 { 0270 Solid::StorageAccess *storageAccess = m_device.as<Solid::StorageAccess>(); 0271 m_mountPoint = storageAccess->filePath(); 0272 Solid::StorageVolume *ssv = m_device.as<Solid::StorageVolume>(); 0273 m_collectionId = ssv ? ssv->uuid() : m_device.udi(); 0274 debug() << "Mounted at: " << m_mountPoint << "collection id:" << m_collectionId; 0275 0276 // read .is_audio_player from filesystem 0277 KConfig config( m_mountPoint + QLatin1Char('/') + s_settingsFileName, KConfig::SimpleConfig ); 0278 KConfigGroup entries = config.group( QString() ); // default group 0279 if( entries.hasKey( s_musicFolderKey ) ) 0280 { 0281 m_musicUrl = QUrl::fromLocalFile( m_mountPoint ); 0282 m_musicUrl = m_musicUrl.adjusted(QUrl::StripTrailingSlash); 0283 m_musicUrl.setPath(m_musicUrl.path() + QLatin1Char('/') + ( entries.readPathEntry( s_musicFolderKey, QString() ) )); 0284 m_musicUrl.setPath( QDir::cleanPath(m_musicUrl.path()) ); 0285 if( !QDir( m_musicUrl.toLocalFile() ).exists() ) 0286 { 0287 QString message = i18n( "File <i>%1</i> suggests that we should use <i>%2</i> " 0288 "as music folder on the device, but it doesn't exist. Falling back to " 0289 "<i>%3</i> instead", m_mountPoint + QLatin1Char('/') + s_settingsFileName, 0290 m_musicUrl.toLocalFile(), m_mountPoint ); 0291 Amarok::Logger::longMessage( message, Amarok::Logger::Warning ); 0292 m_musicUrl = QUrl::fromLocalFile(m_mountPoint); 0293 } 0294 } 0295 else if( !entries.keyList().isEmpty() ) 0296 // config file exists, but has no s_musicFolderKey -> music should be disabled 0297 m_musicUrl = QUrl(); 0298 else 0299 m_musicUrl = QUrl::fromLocalFile(m_mountPoint); // related BR 259849 0300 QString scheme = entries.readEntry( s_musicFilenameSchemeKey ); 0301 m_musicFilenameScheme = !scheme.isEmpty() ? scheme : m_musicFilenameScheme; 0302 m_vfatSafe = entries.readEntry( s_vfatSafeKey, m_vfatSafe ); 0303 m_asciiOnly = entries.readEntry( s_asciiOnlyKey, m_asciiOnly ); 0304 m_postfixThe = entries.readEntry( s_postfixTheKey, m_postfixThe ); 0305 m_replaceSpaces = entries.readEntry( s_replaceSpacesKey, m_replaceSpaces ); 0306 m_regexText = entries.readEntry( s_regexTextKey, m_regexText ); 0307 m_replaceText = entries.readEntry( s_replaceTextKey, m_replaceText ); 0308 if( entries.hasKey( s_podcastFolderKey ) ) 0309 { 0310 m_podcastUrl = QUrl::fromLocalFile( m_mountPoint ); 0311 m_podcastUrl = m_podcastUrl.adjusted(QUrl::StripTrailingSlash); 0312 m_podcastUrl.setPath(m_podcastUrl.path() + QLatin1Char('/') + ( entries.readPathEntry( s_podcastFolderKey, QString() ) )); 0313 m_podcastUrl.setPath( QDir::cleanPath(m_podcastUrl.path()) ); 0314 } 0315 m_autoConnect = entries.readEntry( s_autoConnectKey, m_autoConnect ); 0316 m_collectionName = entries.readEntry( s_collectionName, m_collectionName ); 0317 0318 m_mc = QSharedPointer<MemoryCollection>(new MemoryCollection()); 0319 0320 if( m_autoConnect ) 0321 QTimer::singleShot( 0, this, &UmsCollection::slotParseTracks ); 0322 } 0323 0324 bool 0325 UmsCollection::possiblyContainsTrack( const QUrl &url ) const 0326 { 0327 //not initialized yet. 0328 if( m_mc.isNull() ) 0329 return false; 0330 0331 QString u = QUrl::fromPercentEncoding( url.url().toUtf8() ); 0332 return u.startsWith( m_mountPoint ) || u.startsWith( "file://" + m_mountPoint ); 0333 } 0334 0335 Meta::TrackPtr 0336 UmsCollection::trackForUrl( const QUrl &url ) 0337 { 0338 //not initialized yet. 0339 if( m_mc.isNull() ) 0340 return Meta::TrackPtr(); 0341 0342 QString uid = QUrl::fromPercentEncoding( url.url().toUtf8() ); 0343 if( uid.startsWith("file://") ) 0344 uid = uid.remove( 0, 7 ); 0345 return m_mc->trackMap().value( uid, Meta::TrackPtr() ); 0346 } 0347 0348 QueryMaker * 0349 UmsCollection::queryMaker() 0350 { 0351 return new MemoryQueryMaker( m_mc.toWeakRef(), collectionId() ); 0352 } 0353 0354 QString 0355 UmsCollection::uidUrlProtocol() const 0356 { 0357 return QStringLiteral( "file://" ); 0358 } 0359 0360 QString 0361 UmsCollection::collectionId() const 0362 { 0363 return m_collectionId; 0364 } 0365 0366 QString 0367 UmsCollection::prettyName() const 0368 { 0369 QString actualName; 0370 if( !m_collectionName.isEmpty() ) 0371 actualName = m_collectionName; 0372 else if( !m_device.description().isEmpty() ) 0373 actualName = m_device.description(); 0374 else 0375 { 0376 actualName = m_device.vendor().simplified(); 0377 if( !actualName.isEmpty() ) 0378 actualName += ' '; 0379 actualName += m_device.product().simplified(); 0380 } 0381 0382 if( m_tracksParsed ) 0383 return actualName; 0384 else 0385 return i18nc( "Name of the USB Mass Storage collection that has not yet been " 0386 "activated. See also the 'Activate This Collection' action; %1 is " 0387 "actual collection name", "%1 (not activated)", actualName ); 0388 } 0389 0390 QIcon 0391 UmsCollection::icon() const 0392 { 0393 if( m_device.icon().isEmpty() ) 0394 return QIcon::fromTheme( "drive-removable-media-usb-pendrive" ); 0395 else 0396 return QIcon::fromTheme( m_device.icon() ); 0397 } 0398 0399 bool 0400 UmsCollection::hasCapacity() const 0401 { 0402 if( m_device.isValid() && m_device.is<Solid::StorageAccess>() ) 0403 return m_device.as<Solid::StorageAccess>()->isAccessible(); 0404 return false; 0405 } 0406 0407 float 0408 UmsCollection::usedCapacity() const 0409 { 0410 return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).used(); 0411 } 0412 0413 float 0414 UmsCollection::totalCapacity() const 0415 { 0416 return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).size(); 0417 } 0418 0419 CollectionLocation * 0420 UmsCollection::location() 0421 { 0422 return new UmsCollectionLocation( this ); 0423 } 0424 0425 bool 0426 UmsCollection::isOrganizable() const 0427 { 0428 return isWritable(); 0429 } 0430 0431 bool 0432 UmsCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const 0433 { 0434 switch( type ) 0435 { 0436 case Capabilities::Capability::Actions: 0437 case Capabilities::Capability::Transcode: 0438 return true; 0439 default: 0440 return false; 0441 } 0442 } 0443 0444 Capabilities::Capability * 0445 UmsCollection::createCapabilityInterface( Capabilities::Capability::Type type ) 0446 { 0447 switch( type ) 0448 { 0449 case Capabilities::Capability::Actions: 0450 { 0451 QList<QAction *> actions; 0452 if( m_tracksParsed ) 0453 { 0454 actions << m_configureAction; 0455 actions << m_ejectAction; 0456 } 0457 else 0458 { 0459 actions << m_parseAction; 0460 } 0461 return new Capabilities::ActionsCapability( actions ); 0462 } 0463 case Capabilities::Capability::Transcode: 0464 return new UmsTranscodeCapability( m_mountPoint + QLatin1Char('/') + s_settingsFileName, 0465 s_transcodingGroup ); 0466 default: 0467 return nullptr; 0468 } 0469 } 0470 0471 void 0472 UmsCollection::metadataChanged(const Meta::TrackPtr &track ) 0473 { 0474 if( MemoryMeta::MapChanger( m_mc.data() ).trackChanged( track ) ) 0475 // big-enough change: 0476 Q_EMIT startUpdateTimer(); 0477 } 0478 0479 QUrl 0480 UmsCollection::organizedUrl( const Meta::TrackPtr &track, const QString &fileExtension ) const 0481 { 0482 TrackOrganizer trackOrganizer( Meta::TrackList() << track ); 0483 //%folder% prefix required to get absolute url. 0484 trackOrganizer.setFormatString( "%collectionroot%/" + m_musicFilenameScheme + ".%filetype%" ); 0485 trackOrganizer.setVfatSafe( m_vfatSafe ); 0486 trackOrganizer.setAsciiOnly( m_asciiOnly ); 0487 trackOrganizer.setFolderPrefix( m_musicUrl.path() ); 0488 trackOrganizer.setPostfixThe( m_postfixThe ); 0489 trackOrganizer.setReplaceSpaces( m_replaceSpaces ); 0490 trackOrganizer.setReplace( m_regexText, m_replaceText ); 0491 if( !fileExtension.isEmpty() ) 0492 trackOrganizer.setTargetFileExtension( fileExtension ); 0493 0494 return QUrl::fromLocalFile( trackOrganizer.getDestinations().value( track ) ); 0495 } 0496 0497 void 0498 UmsCollection::slotDestroy() 0499 { 0500 //TODO: stop scanner if running 0501 //unregister PlaylistProvider 0502 //CollectionManager will call destructor. 0503 Q_EMIT remove(); 0504 } 0505 0506 void 0507 UmsCollection::slotEject() 0508 { 0509 slotDestroy(); 0510 Solid::StorageAccess *storageAccess = m_device.as<Solid::StorageAccess>(); 0511 storageAccess->teardown(); 0512 } 0513 0514 void 0515 UmsCollection::slotTrackAdded( const QUrl &location ) 0516 { 0517 Q_ASSERT( m_musicUrl.isParentOf( location ) || m_musicUrl.matches( location , QUrl::StripTrailingSlash) ); 0518 MetaFile::Track *fileTrack = new MetaFile::Track( location ); 0519 fileTrack->setCollection( this ); 0520 Meta::TrackPtr fileTrackPtr = Meta::TrackPtr( fileTrack ); 0521 Meta::TrackPtr proxyTrack = MemoryMeta::MapChanger( m_mc.data() ).addTrack( fileTrackPtr ); 0522 if( proxyTrack ) 0523 { 0524 subscribeTo( fileTrackPtr ); 0525 Q_EMIT startUpdateTimer(); 0526 } 0527 else 0528 warning() << __PRETTY_FUNCTION__ << "Failed to add" << fileTrackPtr->playableUrl() 0529 << "to MemoryCollection. Perhaps already there?!?"; 0530 } 0531 0532 void 0533 UmsCollection::slotTrackRemoved( const Meta::TrackPtr &track ) 0534 { 0535 Meta::TrackPtr removedTrack = MemoryMeta::MapChanger( m_mc.data() ).removeTrack( track ); 0536 if( removedTrack ) 0537 { 0538 unsubscribeFrom( removedTrack ); 0539 // we only added MetaFile::Tracks, following static cast is safe 0540 static_cast<MetaFile::Track*>( removedTrack.data() )->setCollection( nullptr ); 0541 Q_EMIT startUpdateTimer(); 0542 } 0543 else 0544 warning() << __PRETTY_FUNCTION__ << "Failed to remove" << track->playableUrl() 0545 << "from MemoryCollection. Perhaps it was never there?"; 0546 } 0547 0548 void 0549 UmsCollection::collectionUpdated() 0550 { 0551 m_lastUpdated = QDateTime::currentMSecsSinceEpoch(); 0552 Q_EMIT updated(); 0553 } 0554 0555 void 0556 UmsCollection::slotParseTracks() 0557 { 0558 if( !m_scanManager ) 0559 { 0560 m_scanManager = new GenericScanManager( this ); 0561 connect( m_scanManager, &GenericScanManager::directoryScanned, 0562 this, &UmsCollection::slotDirectoryScanned ); 0563 } 0564 0565 m_tracksParsed = true; 0566 m_scanManager->requestScan( QList<QUrl>() << m_musicUrl, GenericScanManager::FullScan ); 0567 } 0568 0569 void 0570 UmsCollection::slotParseActionTriggered() 0571 { 0572 if( m_mc->trackMap().isEmpty() ) 0573 QTimer::singleShot( 0, this, &UmsCollection::slotParseTracks ); 0574 } 0575 0576 void 0577 UmsCollection::slotConfigure() 0578 { 0579 QDialog umsSettingsDialog; 0580 QWidget *settingsWidget = new QWidget( &umsSettingsDialog ); 0581 QScopedPointer<Capabilities::TranscodeCapability> tc( create<Capabilities::TranscodeCapability>() ); 0582 0583 Ui::UmsConfiguration *settings = new Ui::UmsConfiguration(); 0584 settings->setupUi( settingsWidget ); 0585 0586 settings->m_autoConnect->setChecked( m_autoConnect ); 0587 0588 settings->m_musicFolder->setMode( KFile::Directory ); 0589 settings->m_musicCheckBox->setChecked( !m_musicUrl.isEmpty() ); 0590 settings->m_musicWidget->setEnabled( settings->m_musicCheckBox->isChecked() ); 0591 settings->m_musicFolder->setUrl( m_musicUrl.isEmpty() ? QUrl::fromLocalFile( m_mountPoint ) : m_musicUrl ); 0592 settings->m_transcodeConfig->fillInChoices( tc->savedConfiguration() ); 0593 0594 settings->m_podcastFolder->setMode( KFile::Directory ); 0595 settings->m_podcastCheckBox->setChecked( !m_podcastUrl.isEmpty() ); 0596 settings->m_podcastWidget->setEnabled( settings->m_podcastCheckBox->isChecked() ); 0597 settings->m_podcastFolder->setUrl( m_podcastUrl.isEmpty() ? QUrl::fromLocalFile( m_mountPoint ) 0598 : m_podcastUrl ); 0599 0600 settings->m_collectionName->setText( prettyName() ); 0601 0602 OrganizeCollectionWidget *layoutWidget = new OrganizeCollectionWidget; 0603 //TODO: save the setting that are normally written in onAccept() 0604 // connect( this, SIGNAL(accepted()), &layoutWidget, SLOT(onAccept()) ); 0605 QVBoxLayout *layout = new QVBoxLayout; 0606 layout->addWidget( layoutWidget ); 0607 settings->m_filenameSchemeBox->setLayout( layout ); 0608 //hide the unuse preset selector. 0609 //TODO: change the presets to concurrent presets for regular albums v.s. compilations 0610 // layoutWidget.setformatPresetVisible( false ); 0611 layoutWidget->setScheme( m_musicFilenameScheme ); 0612 0613 OrganizeCollectionOptionWidget *optionsWidget = new OrganizeCollectionOptionWidget; 0614 optionsWidget->setVfatCompatible( m_vfatSafe ); 0615 optionsWidget->setAsciiOnly( m_asciiOnly ); 0616 optionsWidget->setPostfixThe( m_postfixThe ); 0617 optionsWidget->setReplaceSpaces( m_replaceSpaces ); 0618 optionsWidget->setRegexpText( m_regexText ); 0619 optionsWidget->setReplaceText( m_replaceText ); 0620 0621 layout->addWidget( optionsWidget ); 0622 0623 umsSettingsDialog.setLayout( new QVBoxLayout ); 0624 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel ); 0625 connect( buttonBox, &QDialogButtonBox::accepted, &umsSettingsDialog, &QDialog::accept ); 0626 connect( buttonBox, &QDialogButtonBox::rejected, &umsSettingsDialog, &QDialog::reject ); 0627 umsSettingsDialog.layout()->addWidget( settingsWidget ); 0628 umsSettingsDialog.layout()->addWidget( buttonBox ); 0629 umsSettingsDialog.setWindowTitle( i18n( "Configure USB Mass Storage Device" ) ); 0630 0631 if( umsSettingsDialog.exec() == QDialog::Accepted ) 0632 { 0633 debug() << "accepted"; 0634 0635 if( settings->m_musicCheckBox->isChecked() ) 0636 { 0637 if( settings->m_musicFolder->url() != m_musicUrl ) 0638 { 0639 debug() << "music location changed from " << m_musicUrl.toLocalFile() << " to "; 0640 debug() << settings->m_musicFolder->url().toLocalFile(); 0641 m_musicUrl = settings->m_musicFolder->url(); 0642 //TODO: reparse music 0643 } 0644 QString scheme = layoutWidget->getParsableScheme().simplified(); 0645 //protect against empty string. 0646 if( !scheme.isEmpty() ) 0647 m_musicFilenameScheme = scheme; 0648 } 0649 else 0650 { 0651 debug() << "music support is disabled"; 0652 m_musicUrl = QUrl(); 0653 //TODO: remove all tracks from the MemoryCollection. 0654 } 0655 0656 m_asciiOnly = optionsWidget->asciiOnly(); 0657 m_postfixThe = optionsWidget->postfixThe(); 0658 m_replaceSpaces = optionsWidget->replaceSpaces(); 0659 m_regexText = optionsWidget->regexpText(); 0660 m_replaceText = optionsWidget->replaceText(); 0661 m_collectionName = settings->m_collectionName->text(); 0662 0663 if( settings->m_podcastCheckBox->isChecked() ) 0664 { 0665 if( settings->m_podcastFolder->url() != m_podcastUrl ) 0666 { 0667 debug() << "podcast location changed from " << m_podcastUrl << " to "; 0668 debug() << settings->m_podcastFolder->url().url(); 0669 m_podcastUrl = QUrl(settings->m_podcastFolder->url()); 0670 //TODO: reparse podcasts 0671 } 0672 } 0673 else 0674 { 0675 debug() << "podcast support is disabled"; 0676 m_podcastUrl = QUrl(); 0677 //TODO: remove the PodcastProvider 0678 } 0679 0680 m_autoConnect = settings->m_autoConnect->isChecked(); 0681 if( !m_musicUrl.isEmpty() && m_autoConnect ) 0682 QTimer::singleShot( 0, this, &UmsCollection::slotParseTracks ); 0683 0684 // write the data to the on-disk file 0685 KConfig config( m_mountPoint + QLatin1Char('/') + s_settingsFileName, KConfig::SimpleConfig ); 0686 KConfigGroup entries = config.group( QString() ); // default group 0687 if( !m_musicUrl.isEmpty() ) 0688 entries.writePathEntry( s_musicFolderKey, QDir( m_mountPoint ).relativeFilePath( m_musicUrl.toLocalFile() )); 0689 else 0690 entries.deleteEntry( s_musicFolderKey ); 0691 entries.writeEntry( s_musicFilenameSchemeKey, m_musicFilenameScheme ); 0692 entries.writeEntry( s_vfatSafeKey, m_vfatSafe ); 0693 entries.writeEntry( s_asciiOnlyKey, m_asciiOnly ); 0694 entries.writeEntry( s_postfixTheKey, m_postfixThe ); 0695 entries.writeEntry( s_replaceSpacesKey, m_replaceSpaces ); 0696 entries.writeEntry( s_regexTextKey, m_regexText ); 0697 entries.writeEntry( s_replaceTextKey, m_replaceText ); 0698 if( !m_podcastUrl.isEmpty() ) 0699 entries.writePathEntry( s_podcastFolderKey, QDir( m_mountPoint ).relativeFilePath( m_podcastUrl.toLocalFile() )); 0700 else 0701 entries.deleteEntry( s_podcastFolderKey ); 0702 entries.writeEntry( s_autoConnectKey, m_autoConnect ); 0703 entries.writeEntry( s_collectionName, m_collectionName ); 0704 config.sync(); 0705 0706 tc->setSavedConfiguration( settings->m_transcodeConfig->currentChoice() ); 0707 } 0708 0709 delete settings; 0710 } 0711 0712 void 0713 UmsCollection::slotDirectoryScanned( QSharedPointer<CollectionScanner::Directory> dir ) 0714 { 0715 debug() << "directory scanned: " << dir->path(); 0716 if( dir->tracks().isEmpty() ) 0717 { 0718 debug() << "does not have tracks"; 0719 return; 0720 } 0721 0722 foreach( const CollectionScanner::Track *scannerTrack, dir->tracks() ) 0723 { 0724 //TODO: use proxy tracks so no real file read is required 0725 // following method calls startUpdateTimer(), no need to Q_EMIT updated() 0726 slotTrackAdded( QUrl::fromLocalFile(scannerTrack->path()) ); 0727 } 0728 0729 //TODO: read playlists 0730 } 0731 0732 void 0733 UmsCollection::slotStartUpdateTimer() 0734 { 0735 // there are no concurrency problems, this method can only be called from the main 0736 // thread and that's where the timer fires 0737 if( m_updateTimer.isActive() ) 0738 return; // already running, nothing to do 0739 0740 // number of milliseconds to next desired update, may be negative 0741 int timeout = m_lastUpdated + 1000 - QDateTime::currentMSecsSinceEpoch(); 0742 // give at least 50 msecs to catch multi-tracks edits nicely on the first frame 0743 m_updateTimer.start( qBound( 50, timeout, 1000 ) ); 0744 }