File indexing completed on 2025-01-05 04:25:55
0001 /**************************************************************************************** 0002 * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> * 0003 * Copyright (c) 2008 Jason A. Donenfeld <Jason@zx2c4.com> * 0004 * Copyright (c) 2010 Casey Link <unnamedrambler@gmail.com> * 0005 * Copyright (c) 2010 Teo Mrnjavac <teo@kde.org> * 0006 * * 0007 * This program is free software; you can redistribute it and/or modify it under * 0008 * the terms of the GNU General Public License as published by the Free Software * 0009 * Foundation; either version 2 of the License, or (at your option) any later * 0010 * version. * 0011 * * 0012 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0013 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0014 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0015 * * 0016 * You should have received a copy of the GNU General Public License along with * 0017 * this program. If not, see <http://www.gnu.org/licenses/>. * 0018 ****************************************************************************************/ 0019 0020 #define DEBUG_PREFIX "SqlCollectionLocation" 0021 0022 #include "SqlCollectionLocation.h" 0023 0024 #include "MetaTagLib.h" // for getting the uid 0025 #include "core/collections/CollectionLocationDelegate.h" 0026 #include <core/storage/SqlStorage.h> 0027 #include "core/logger/Logger.h" 0028 #include "core/support/Components.h" 0029 #include "core/support/Debug.h" 0030 #include "core/meta/Meta.h" 0031 #include "core/meta/support/MetaUtility.h" 0032 #include "core/transcoding/TranscodingController.h" 0033 #include "core-impl/collections/db/MountPointManager.h" 0034 #include "core-impl/collections/db/sql/SqlCollection.h" 0035 #include "core-impl/collections/db/sql/SqlMeta.h" 0036 #include "transcoding/TranscodingJob.h" 0037 0038 #include <QDir> 0039 #include <QFile> 0040 #include <QFileInfo> 0041 0042 #include <KDiskFreeSpaceInfo> 0043 #include <KFileItem> 0044 #include <KJob> 0045 #include <KIO/DeleteJob> 0046 #include <KIO/Job> 0047 #include <KConfigGroup> 0048 #include <KLocalizedString> 0049 0050 using namespace Collections; 0051 0052 SqlCollectionLocation::SqlCollectionLocation( SqlCollection *collection ) 0053 : CollectionLocation( collection ) 0054 , m_collection( collection ) 0055 , m_delegateFactory( nullptr ) 0056 , m_overwriteFiles( false ) 0057 , m_transferjob( ) 0058 { 0059 //nothing to do 0060 } 0061 0062 SqlCollectionLocation::~SqlCollectionLocation() 0063 { 0064 //nothing to do 0065 delete m_delegateFactory; 0066 } 0067 0068 QString 0069 SqlCollectionLocation::prettyLocation() const 0070 { 0071 return i18n( "Local Collection" ); 0072 } 0073 0074 QStringList 0075 SqlCollectionLocation::actualLocation() const 0076 { 0077 return m_collection->mountPointManager()->collectionFolders(); 0078 } 0079 0080 bool 0081 SqlCollectionLocation::isWritable() const 0082 { 0083 // TODO: This function is also called when removing files to check 0084 // if the tracks can be removed. In such a case we should not check the space 0085 0086 // The collection is writable if there exists a path that has more than 0087 // 500 MB free space. 0088 bool path_exists_with_space = false; 0089 bool path_exists_writable = false; 0090 QStringList folders = actualLocation(); 0091 foreach( const QString &path, folders ) 0092 { 0093 float used = KDiskFreeSpaceInfo::freeSpaceInfo( path ).used(); 0094 float total = KDiskFreeSpaceInfo::freeSpaceInfo( path ).size(); 0095 0096 if( total <= 0 ) // protect against div by zero 0097 continue; //How did this happen? 0098 0099 float free_space = total - used; 0100 if( free_space >= 500*1000*1000 ) // ~500 megabytes 0101 path_exists_with_space = true; 0102 0103 QFileInfo info( path ); 0104 if( info.isWritable() ) 0105 path_exists_writable = true; 0106 } 0107 return path_exists_with_space && path_exists_writable; 0108 } 0109 0110 bool 0111 SqlCollectionLocation::isOrganizable() const 0112 { 0113 return true; 0114 } 0115 0116 bool 0117 SqlCollectionLocation::remove( const Meta::TrackPtr &track ) 0118 { 0119 DEBUG_BLOCK 0120 Q_ASSERT( track ); 0121 0122 if( track->inCollection() && 0123 track->collection()->collectionId() == m_collection->collectionId() ) 0124 { 0125 bool removed; 0126 QUrl src = track->playableUrl(); 0127 if( isGoingToRemoveSources() ) // is organize operation? 0128 { 0129 SqlCollectionLocation* destinationloc = qobject_cast<SqlCollectionLocation*>( destination() ); 0130 if( destinationloc ) 0131 { 0132 src = destinationloc->m_originalUrls[track]; 0133 if( src == track->playableUrl() ) 0134 return false; 0135 } 0136 } 0137 // we are going to delete it from the database only if is no longer on disk 0138 removed = !QFile::exists( src.path() ); 0139 if( removed ) 0140 static_cast<Meta::SqlTrack*>(const_cast<Meta::Track*>(track.data()))->remove(); 0141 0142 return removed; 0143 } 0144 else 0145 { 0146 debug() << "Remove Failed"; 0147 return false; 0148 } 0149 } 0150 0151 bool 0152 SqlCollectionLocation::insert( const Meta::TrackPtr &track, const QString &path ) 0153 { 0154 if( !QFile::exists( path ) ) 0155 { 0156 warning() << Q_FUNC_INFO << "file" << path << "does not exist, not inserting into db"; 0157 return false; 0158 } 0159 0160 // -- the target path 0161 SqlRegistry *registry = m_collection->registry(); 0162 int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile( path ) ); 0163 QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, path ); 0164 int directoryId = registry->getDirectory( QFileInfo( path ).path() ); 0165 0166 // -- the track uid (we can't use the original one from the old collection) 0167 Meta::FieldHash fileTags = Meta::Tag::readTags( path ); 0168 QString uid = fileTags.value( Meta::valUniqueId ).toString(); 0169 uid = m_collection->generateUidUrl( uid ); // add the right prefix 0170 0171 // -- the track from the registry 0172 Meta::SqlTrackPtr metaTrack; 0173 metaTrack = Meta::SqlTrackPtr::staticCast( registry->getTrackFromUid( uid ) ); 0174 0175 if( metaTrack ) 0176 { 0177 warning() << "Location is inserting a file with the same uid as an already existing one."; 0178 metaTrack->setUrl( deviceId, rpath, directoryId ); 0179 } else 0180 metaTrack = Meta::SqlTrackPtr::staticCast( registry->getTrack( deviceId, rpath, directoryId, uid ) ); 0181 0182 Meta::ConstStatisticsPtr origStats = track->statistics(); 0183 0184 // -- set the values 0185 metaTrack->setWriteFile( false ); // no need to write the tags back 0186 metaTrack->beginUpdate(); 0187 0188 if( !track->name().isEmpty() ) 0189 metaTrack->setTitle( track->name() ); 0190 if( track->album() ) 0191 metaTrack->setAlbum( track->album()->name() ); 0192 if( track->artist() ) 0193 metaTrack->setArtist( track->artist()->name() ); 0194 if( track->composer() ) 0195 metaTrack->setComposer( track->composer()->name() ); 0196 if( track->year() && track->year()->year() > 0 ) 0197 metaTrack->setYear( track->year()->year() ); 0198 if( track->genre() ) 0199 metaTrack->setGenre( track->genre()->name() ); 0200 0201 if( track->bpm() > 0 ) 0202 metaTrack->setBpm( track->bpm() ); 0203 if( !track->comment().isEmpty() ) 0204 metaTrack->setComment( track->comment() ); 0205 0206 if( origStats->score() > 0 ) 0207 metaTrack->setScore( origStats->score() ); 0208 if( origStats->rating() > 0 ) 0209 metaTrack->setRating( origStats->rating() ); 0210 0211 /* These tags change when transcoding. Prefer to read those from file */ 0212 if( fileTags.value( Meta::valLength, 0 ).toLongLong() > 0 ) 0213 metaTrack->setLength( fileTags.value( Meta::valLength ).value<qint64>() ); 0214 else if( track->length() > 0 ) 0215 metaTrack->setLength( track->length() ); 0216 // the filesize is updated every time after the 0217 // file is changed. Doesn't make sense to set it. 0218 if( fileTags.value( Meta::valSamplerate, 0 ).toInt() > 0 ) 0219 metaTrack->setSampleRate( fileTags.value( Meta::valSamplerate ).toInt() ); 0220 else if( track->sampleRate() > 0 ) 0221 metaTrack->setSampleRate( track->sampleRate() ); 0222 if( fileTags.value( Meta::valBitrate, 0 ).toInt() > 0 ) 0223 metaTrack->setBitrate( fileTags.value( Meta::valBitrate ).toInt() ); 0224 else if( track->bitrate() > 0 ) 0225 metaTrack->setBitrate( track->bitrate() ); 0226 0227 // createDate is already set in Track constructor 0228 if( track->modifyDate().isValid() ) 0229 metaTrack->setModifyDate( track->modifyDate() ); 0230 0231 if( track->trackNumber() > 0 ) 0232 metaTrack->setTrackNumber( track->trackNumber() ); 0233 if( track->discNumber() > 0 ) 0234 metaTrack->setDiscNumber( track->discNumber() ); 0235 0236 if( origStats->lastPlayed().isValid() ) 0237 metaTrack->setLastPlayed( origStats->lastPlayed() ); 0238 if( origStats->firstPlayed().isValid() ) 0239 metaTrack->setFirstPlayed( origStats->firstPlayed() ); 0240 if( origStats->playCount() > 0 ) 0241 metaTrack->setPlayCount( origStats->playCount() ); 0242 0243 Meta::ReplayGainTag modes[] = { Meta::ReplayGain_Track_Gain, 0244 Meta::ReplayGain_Track_Peak, 0245 Meta::ReplayGain_Album_Gain, 0246 Meta::ReplayGain_Album_Peak }; 0247 for( int i=0; i<4; i++ ) 0248 if( track->replayGain( modes[i] ) != 0 ) 0249 metaTrack->setReplayGain( modes[i], track->replayGain( modes[i] ) ); 0250 0251 Meta::LabelList labels = track->labels(); 0252 foreach( Meta::LabelPtr label, labels ) 0253 metaTrack->addLabel( label ); 0254 0255 if( fileTags.value( Meta::valFormat, int(Amarok::Unknown) ).toInt() != int(Amarok::Unknown) ) 0256 metaTrack->setType( Amarok::FileType( fileTags.value( Meta::valFormat ).toInt() ) ); 0257 else if( Amarok::FileTypeSupport::fileType( track->type() ) != Amarok::Unknown ) 0258 metaTrack->setType( Amarok::FileTypeSupport::fileType( track->type() ) ); 0259 0260 // Used to be updated after changes commit to prevent crash on NULL pointer access 0261 // if metaTrack had no album. 0262 if( track->album() && metaTrack->album() ) 0263 { 0264 if( track->album()->hasAlbumArtist() && !metaTrack->album()->hasAlbumArtist() ) 0265 metaTrack->setAlbumArtist( track->album()->albumArtist()->name() ); 0266 0267 if( track->album()->hasImage() && !metaTrack->album()->hasImage() ) 0268 metaTrack->album()->setImage( track->album()->image() ); 0269 } 0270 0271 metaTrack->endUpdate(); 0272 metaTrack->setWriteFile( true ); 0273 0274 // we have a first shot at the meta data (especially ratings and playcounts from media 0275 // collections) but we still need to trigger the collection scanner 0276 // to get the album and other meta data correct. 0277 // TODO m_collection->directoryWatcher()->delayedIncrementalScan( QFileInfo(url).path() ); 0278 0279 return true; 0280 } 0281 0282 void 0283 SqlCollectionLocation::showDestinationDialog( const Meta::TrackList &tracks, 0284 bool removeSources, 0285 const Transcoding::Configuration &configuration ) 0286 { 0287 DEBUG_BLOCK 0288 setGoingToRemoveSources( removeSources ); 0289 0290 KIO::filesize_t transferSize = 0; 0291 foreach( Meta::TrackPtr track, tracks ) 0292 transferSize += track->filesize(); 0293 0294 const QStringList actual_folders = actualLocation(); // the folders in the collection 0295 QStringList available_folders; // the folders which have freespace available 0296 foreach(const QString &path, actual_folders) 0297 { 0298 if( path.isEmpty() ) 0299 continue; 0300 debug() << "Path" << path; 0301 KDiskFreeSpaceInfo spaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo( path ); 0302 if( !spaceInfo.isValid() ) 0303 continue; 0304 0305 KIO::filesize_t totalCapacity = spaceInfo.size(); 0306 KIO::filesize_t used = spaceInfo.used(); 0307 0308 KIO::filesize_t freeSpace = totalCapacity - used; 0309 0310 debug() << "used:" << used; 0311 debug() << "total:" << totalCapacity; 0312 debug() << "Free space" << freeSpace; 0313 debug() << "transfersize" << transferSize; 0314 0315 if( totalCapacity <= 0 ) // protect against div by zero 0316 continue; //How did this happen? 0317 0318 QFileInfo info( path ); 0319 0320 // since bad things happen when drives become totally full 0321 // we make sure there is at least ~500MB left 0322 // finally, ensure the path is writable 0323 debug() << ( freeSpace - transferSize ); 0324 if( ( freeSpace - transferSize ) > 1024*1024*500 && info.isWritable() ) 0325 available_folders << path; 0326 } 0327 0328 if( available_folders.size() <= 0 ) 0329 { 0330 debug() << "No space available or not writable"; 0331 CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); 0332 delegate->notWriteable( this ); 0333 abort(); 0334 return; 0335 } 0336 0337 OrganizeCollectionDelegate *delegate = m_delegateFactory->createDelegate(); 0338 delegate->setTracks( tracks ); 0339 delegate->setFolders( available_folders ); 0340 delegate->setIsOrganizing( ( collection() == source()->collection() ) ); 0341 delegate->setTranscodingConfiguration( configuration ); 0342 delegate->setCaption( operationText( configuration ) ); 0343 0344 connect( delegate, &OrganizeCollectionDelegate::accepted, this, &SqlCollectionLocation::slotDialogAccepted ); 0345 connect( delegate, &OrganizeCollectionDelegate::rejected, this, &SqlCollectionLocation::slotDialogRejected ); 0346 delegate->show(); 0347 } 0348 0349 void 0350 SqlCollectionLocation::slotDialogAccepted() 0351 { 0352 DEBUG_BLOCK 0353 sender()->deleteLater(); 0354 OrganizeCollectionDelegate *ocDelegate = qobject_cast<OrganizeCollectionDelegate*>( sender() ); 0355 m_destinations = ocDelegate->destinations(); 0356 m_overwriteFiles = ocDelegate->overwriteDestinations(); 0357 if( isGoingToRemoveSources() ) 0358 { 0359 CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); 0360 const bool del = delegate->reallyMove( this, m_destinations.keys() ); 0361 if( !del ) 0362 { 0363 abort(); 0364 return; 0365 } 0366 } 0367 slotShowDestinationDialogDone(); 0368 } 0369 0370 void 0371 SqlCollectionLocation::slotDialogRejected() 0372 { 0373 DEBUG_BLOCK 0374 sender()->deleteLater(); 0375 abort(); 0376 } 0377 0378 void 0379 SqlCollectionLocation::slotJobFinished( KJob *job ) 0380 { 0381 DEBUG_BLOCK 0382 0383 Meta::TrackPtr track = m_jobs.value( job ); 0384 if( job->error() && job->error() != KIO::ERR_FILE_ALREADY_EXIST ) 0385 { 0386 //TODO: proper error handling 0387 warning() << "An error occurred when copying a file: " << job->errorString(); 0388 source()->transferError( track, KIO::buildErrorString( job->error(), job->errorString() ) ); 0389 m_destinations.remove( track ); 0390 } 0391 else 0392 source()->transferSuccessful( track ); 0393 0394 m_jobs.remove( job ); 0395 job->deleteLater(); 0396 0397 } 0398 0399 void 0400 SqlCollectionLocation::slotRemoveJobFinished( KJob *job ) 0401 { 0402 DEBUG_BLOCK 0403 Meta::TrackPtr track = m_removejobs.value( job ); 0404 if( job->error() ) 0405 { 0406 //TODO: proper error handling 0407 warning() << "An error occurred when removing a file: " << job->errorString(); 0408 } 0409 0410 // -- remove the track from the database if it's gone 0411 if( !QFile(track->playableUrl().path()).exists() ) 0412 { 0413 // Remove the track from the database 0414 remove( track ); 0415 0416 //we assume that KIO works correctly... 0417 transferSuccessful( track ); 0418 } 0419 else 0420 { 0421 transferError( track, KIO::buildErrorString( job->error(), job->errorString() ) ); 0422 } 0423 0424 m_removejobs.remove( job ); 0425 job->deleteLater(); 0426 0427 if( !startNextRemoveJob() ) 0428 { 0429 slotRemoveOperationFinished(); 0430 } 0431 0432 } 0433 0434 void SqlCollectionLocation::slotTransferJobFinished( KJob* job ) 0435 { 0436 DEBUG_BLOCK 0437 if( job->error() ) 0438 { 0439 debug() << job->errorText(); 0440 } 0441 // filter the list of destinations to only include tracks 0442 // that were successfully copied 0443 foreach( const Meta::TrackPtr &track, m_destinations.keys() ) 0444 { 0445 if( QFile::exists( m_destinations[ track ] ) ) 0446 insert( track, m_destinations[ track ] ); 0447 m_originalUrls[track] = track->playableUrl(); 0448 } 0449 debug () << "m_originalUrls" << m_originalUrls; 0450 slotCopyOperationFinished(); 0451 } 0452 0453 void SqlCollectionLocation::slotTransferJobAborted() 0454 { 0455 DEBUG_BLOCK 0456 if( !m_transferjob ) 0457 return; 0458 m_transferjob->kill(); 0459 // filter the list of destinations to only include tracks 0460 // that were successfully copied 0461 foreach( const Meta::TrackPtr &track, m_destinations.keys() ) 0462 { 0463 if( QFile::exists( m_destinations[ track ] ) ) 0464 insert( track, m_destinations[ track ] ); // was already copied, so have to insert it in the db 0465 m_originalUrls[track] = track->playableUrl(); 0466 } 0467 abort(); 0468 } 0469 0470 0471 void 0472 SqlCollectionLocation::copyUrlsToCollection( const QMap<Meta::TrackPtr, QUrl> &sources, 0473 const Transcoding::Configuration &configuration ) 0474 { 0475 DEBUG_BLOCK 0476 m_sources = sources; 0477 0478 QString statusBarTxt = operationInProgressText( configuration, sources.count() ); 0479 m_transferjob = new TransferJob( this, configuration ); 0480 Amarok::Logger::newProgressOperation( m_transferjob, statusBarTxt, this, 0481 &SqlCollectionLocation::slotTransferJobAborted ); 0482 connect( m_transferjob, &Collections::TransferJob::result, 0483 this, &SqlCollectionLocation::slotTransferJobFinished ); 0484 m_transferjob->start(); 0485 } 0486 0487 void 0488 SqlCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources ) 0489 { 0490 DEBUG_BLOCK 0491 0492 m_removetracks = sources; 0493 0494 if( !startNextRemoveJob() ) //this signal needs to be called no matter what, even if there are no job finishes to call it 0495 slotRemoveOperationFinished(); 0496 } 0497 0498 void 0499 SqlCollectionLocation::setOrganizeCollectionDelegateFactory( OrganizeCollectionDelegateFactory *fac ) 0500 { 0501 m_delegateFactory = fac; 0502 } 0503 0504 bool SqlCollectionLocation::startNextJob( const Transcoding::Configuration &configuration ) 0505 { 0506 DEBUG_BLOCK 0507 if( !m_sources.isEmpty() ) 0508 { 0509 Meta::TrackPtr track = m_sources.keys().first(); 0510 QUrl src = m_sources.take( track ); 0511 0512 QUrl dest = QUrl::fromLocalFile(m_destinations[ track ]); 0513 dest.setPath( QDir::cleanPath(dest.path()) ); 0514 src.setPath( QDir::cleanPath(src.path()) ); 0515 // KIO::file_copy in KF5 needs scheme 0516 if (src.isRelative() && src.host().isEmpty()) { 0517 src.setScheme("file"); 0518 } 0519 0520 bool hasMoodFile = QFile::exists( moodFile( src ).toLocalFile() ); 0521 bool isJustCopy = configuration.isJustCopy( track ); 0522 0523 if( isJustCopy ) 0524 debug() << "copying from " << src << " to " << dest; 0525 else 0526 debug() << "transcoding from " << src << " to " << dest; 0527 0528 KFileItem srcInfo( src ); 0529 if( !srcInfo.isFile() ) 0530 { 0531 warning() << "Source track" << src << "was no file"; 0532 source()->transferError( track, i18n( "Source track does not exist: %1", src.toDisplayString() ) ); 0533 return true; // Attempt to copy/move the next item in m_sources 0534 } 0535 0536 QFileInfo destInfo( dest.toLocalFile() ); 0537 QDir dir = destInfo.dir(); 0538 if( !dir.exists() ) 0539 { 0540 if( !dir.mkpath( "." ) ) 0541 { 0542 warning() << "Could not create directory " << dir; 0543 source()->transferError(track, i18n( "Could not create directory: %1", dir.path() ) ); 0544 return true; // Attempt to copy/move the next item in m_sources 0545 } 0546 } 0547 0548 KIO::JobFlags flags; 0549 if( isJustCopy ) 0550 { 0551 flags = KIO::HideProgressInfo; 0552 if( m_overwriteFiles ) 0553 { 0554 flags |= KIO::Overwrite; 0555 } 0556 } 0557 0558 KJob *job = nullptr; 0559 KJob *moodJob = nullptr; 0560 0561 if( src.matches( dest, QUrl::StripTrailingSlash ) ) 0562 { 0563 warning() << "move to itself found: " << destInfo.absoluteFilePath(); 0564 m_transferjob->slotJobFinished( nullptr ); 0565 if( m_sources.isEmpty() ) 0566 return false; 0567 return true; 0568 } 0569 else if( isGoingToRemoveSources() && source()->collection() == collection() ) 0570 { 0571 debug() << "moving!"; 0572 job = KIO::file_move( src, dest, -1, flags ); 0573 if( hasMoodFile ) 0574 { 0575 QUrl moodSrc = moodFile( src ); 0576 QUrl moodDest = moodFile( dest ); 0577 moodJob = KIO::file_move( moodSrc, moodDest, -1, flags ); 0578 } 0579 } 0580 else 0581 { 0582 //later on in the case that remove is called, the file will be deleted because we didn't apply moveByDestination to the track 0583 if( isJustCopy ) 0584 job = KIO::file_copy( src, dest, -1, flags ); 0585 else 0586 { 0587 QString destPath = dest.path(); 0588 destPath.truncate( dest.path().lastIndexOf( QLatin1Char('.') ) + 1 ); 0589 destPath.append( Amarok::Components::transcodingController()-> 0590 format( configuration.encoder() )->fileExtension() ); 0591 dest.setPath( destPath ); 0592 job = new Transcoding::Job( src, dest, configuration, this ); 0593 job->start(); 0594 } 0595 0596 if( hasMoodFile ) 0597 { 0598 QUrl moodSrc = moodFile( src ); 0599 QUrl moodDest = moodFile( dest ); 0600 moodJob = KIO::file_copy( moodSrc, moodDest, -1, flags ); 0601 } 0602 } 0603 if( job ) //just to be safe 0604 { 0605 connect( job, &KJob::result, this, &SqlCollectionLocation::slotJobFinished ); 0606 connect( job, &KJob::result, m_transferjob, &Collections::TransferJob::slotJobFinished ); 0607 m_transferjob->addSubjob( job ); 0608 0609 if( moodJob ) 0610 { 0611 connect( moodJob, &KJob::result, m_transferjob, &Collections::TransferJob::slotJobFinished ); 0612 m_transferjob->addSubjob( moodJob ); 0613 } 0614 0615 QString name = track->prettyName(); 0616 if( track->artist() ) 0617 name = QStringLiteral( "%1 - %2" ).arg( track->artist()->name(), track->prettyName() ); 0618 0619 if( isJustCopy ) 0620 m_transferjob->emitInfo( i18n( "Transferring: %1", name ) ); 0621 else 0622 m_transferjob->emitInfo( i18n( "Transcoding: %1", name ) ); 0623 m_jobs.insert( job, track ); 0624 return true; 0625 } 0626 debug() << "JOB NULL OMG!11"; 0627 } 0628 return false; 0629 } 0630 0631 bool SqlCollectionLocation::startNextRemoveJob() 0632 { 0633 DEBUG_BLOCK 0634 while ( !m_removetracks.isEmpty() ) 0635 { 0636 Meta::TrackPtr track = m_removetracks.takeFirst(); 0637 // QUrl src = track->playableUrl(); 0638 QUrl src = track->playableUrl(); 0639 QUrl srcMoodFile = moodFile( src ); 0640 0641 debug() << "isGoingToRemoveSources() " << isGoingToRemoveSources(); 0642 if( isGoingToRemoveSources() && destination() ) // is organize operation? 0643 { 0644 SqlCollectionLocation* destinationloc = dynamic_cast<SqlCollectionLocation*>( destination() ); 0645 0646 // src = destinationloc->m_originalUrls[track]; 0647 if( destinationloc && src == QUrl::fromUserInput(destinationloc->m_destinations[track]) ) { 0648 debug() << "src == dst ("<<src<<")"; 0649 continue; 0650 } 0651 } 0652 0653 src.setPath( QDir::cleanPath(src.path()) ); 0654 debug() << "deleting " << src; 0655 KIO::DeleteJob *job = KIO::del( src, KIO::HideProgressInfo ); 0656 if( job ) //just to be safe 0657 { 0658 if( QFile::exists( srcMoodFile.toLocalFile() ) ) 0659 KIO::del( srcMoodFile, KIO::HideProgressInfo ); 0660 0661 connect( job, &KIO::DeleteJob::result, this, &SqlCollectionLocation::slotRemoveJobFinished ); 0662 QString name = track->prettyName(); 0663 if( track->artist() ) 0664 name = QStringLiteral( "%1 - %2" ).arg( track->artist()->name(), track->prettyName() ); 0665 0666 Amarok::Logger::newProgressOperation( job, i18n( "Removing: %1", name ) ); 0667 m_removejobs.insert( job, track ); 0668 return true; 0669 } 0670 break; 0671 } 0672 return false; 0673 } 0674 0675 QUrl 0676 SqlCollectionLocation::moodFile( const QUrl &track ) const 0677 { 0678 QUrl moodPath = track; 0679 QString fileName = moodPath.fileName(); 0680 moodPath = moodPath.adjusted(QUrl::RemoveFilename); 0681 moodPath.setPath(moodPath.path() + '.' + fileName.replace( QRegExp( "(\\.\\w{2,5})$" ), ".mood" ) ); 0682 return moodPath; 0683 } 0684 0685 TransferJob::TransferJob( SqlCollectionLocation * location, const Transcoding::Configuration & configuration ) 0686 : KCompositeJob( nullptr ) 0687 , m_location( location ) 0688 , m_killed( false ) 0689 , m_transcodeFormat( configuration ) 0690 { 0691 setCapabilities( KJob::Killable ); 0692 debug() << "TransferJob::TransferJob"; 0693 } 0694 0695 bool TransferJob::addSubjob( KJob* job ) 0696 { 0697 connect( job, SIGNAL(processedAmount(KJob*, KJob::Unit, qulonglong)), 0698 this, SLOT(propagateProcessedAmount(KJob*, KJob::Unit, qulonglong)) ); 0699 //KCompositeJob::addSubjob doesn't handle progress reporting. 0700 return KCompositeJob::addSubjob( job ); 0701 } 0702 0703 void TransferJob::emitInfo(const QString& message) 0704 { 0705 Q_EMIT infoMessage( this, message ); 0706 } 0707 0708 void TransferJob::slotResult( KJob *job ) 0709 { 0710 // When copying without overwriting some files might already be 0711 // there and it is not a reason for stopping entire transfer. 0712 if ( job->error() == KIO::ERR_FILE_ALREADY_EXIST ) 0713 removeSubjob( job ); 0714 else 0715 KCompositeJob::slotResult( job ); 0716 } 0717 0718 void TransferJob::start() 0719 { 0720 DEBUG_BLOCK 0721 if( m_location == nullptr ) 0722 { 0723 setError( 1 ); 0724 setErrorText( "Location is null!" ); 0725 emitResult(); 0726 return; 0727 } 0728 QTimer::singleShot( 0, this, &TransferJob::doWork ); 0729 } 0730 0731 void TransferJob::doWork() 0732 { 0733 DEBUG_BLOCK 0734 setTotalAmount( KJob::Files, m_location->m_sources.size() ); 0735 setTotalAmount( KJob::Bytes, m_location->m_sources.size() * 1000 ); 0736 setProcessedAmount( KJob::Files, 0 ); 0737 if( !m_location->startNextJob( m_transcodeFormat ) ) 0738 { 0739 if( !hasSubjobs() ) 0740 emitResult(); 0741 } 0742 } 0743 0744 void TransferJob::slotJobFinished( KJob* job ) 0745 { 0746 DEBUG_BLOCK 0747 if( job ) 0748 removeSubjob( job ); 0749 if( m_killed ) 0750 { 0751 debug() << "slotJobFinished entered, but it should be killed!"; 0752 return; 0753 } 0754 setProcessedAmount( KJob::Files, processedAmount( KJob::Files ) + 1 ); 0755 emitPercent( processedAmount( KJob::Files ) * 1000, totalAmount( KJob::Bytes ) ); 0756 debug() << "processed" << processedAmount( KJob::Files ) << " totalAmount" << totalAmount( KJob::Files ); 0757 if( !m_location->startNextJob( m_transcodeFormat ) ) 0758 { 0759 debug() << "sources empty"; 0760 // don't quit if there are still subjobs 0761 if( !hasSubjobs() ) 0762 emitResult(); 0763 else 0764 debug() << "have subjobs"; 0765 } 0766 } 0767 0768 bool TransferJob::doKill() 0769 { 0770 DEBUG_BLOCK 0771 m_killed = true; 0772 foreach( KJob* job, subjobs() ) 0773 { 0774 job->kill(); 0775 } 0776 clearSubjobs(); 0777 return KJob::doKill(); 0778 } 0779 0780 void TransferJob::propagateProcessedAmount( KJob *job, KJob::Unit unit, qulonglong amount ) //SLOT 0781 { 0782 if( unit == KJob::Bytes ) 0783 { 0784 qulonglong currentJobAmount = ( static_cast< qreal >( amount ) / job->totalAmount( KJob::Bytes ) ) * 1000; 0785 0786 setProcessedAmount( KJob::Bytes, processedAmount( KJob::Files ) * 1000 + currentJobAmount ); 0787 emitPercent( processedAmount( KJob::Bytes ), totalAmount( KJob::Bytes ) ); 0788 } 0789 } 0790