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