File indexing completed on 2025-01-05 03:53:54

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2007-04-09
0007  * Description : Collection location management - location helpers.
0008  *
0009  * SPDX-FileCopyrightText: 2007-2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "collectionmanager_p.h"
0016 
0017 namespace Digikam
0018 {
0019 
0020 CollectionLocation CollectionManager::addLocation(const QUrl& fileUrl, const QString& label)
0021 {
0022     qCDebug(DIGIKAM_DATABASE_LOG) << "addLocation" << fileUrl;
0023     QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile();
0024 
0025     if (!locationForPath(path).isNull())
0026     {
0027         return CollectionLocation();
0028     }
0029 
0030     QList<SolidVolumeInfo> volumes = d->listVolumes();
0031     SolidVolumeInfo volume         = d->findVolumeForUrl(fileUrl, volumes);
0032 
0033     if (!volume.isNull())
0034     {
0035         // volume.path has a trailing slash. We want to split in front of this.
0036 
0037         QString specificPath = path.mid(volume.path.length() - 1);
0038         CollectionLocation::Type type;
0039 
0040         if (volume.isRemovable)
0041         {
0042             type = CollectionLocation::VolumeRemovable;
0043         }
0044         else
0045         {
0046             type = CollectionLocation::VolumeHardWired;
0047         }
0048 
0049         ChangingDB changing(d);
0050         CoreDbAccess().db()->addAlbumRoot(type, d->volumeIdentifier(volume), specificPath, label);
0051     }
0052     else
0053     {
0054         // Empty volumes indicates that Solid is not working correctly.
0055 
0056         if (volumes.isEmpty())
0057         {
0058             qCDebug(DIGIKAM_DATABASE_LOG) << "Solid did not return any storage volumes on your system.";
0059             qCDebug(DIGIKAM_DATABASE_LOG) << "This indicates a missing implementation or a problem with your installation";
0060             qCDebug(DIGIKAM_DATABASE_LOG) << "On Linux, check that Solid and HAL are working correctly. "
0061                                              "Problems with RAID partitions have been reported, "
0062                                              "if you have RAID this error may be normal.";
0063             qCDebug(DIGIKAM_DATABASE_LOG) << "On Windows, Solid may not be fully implemented, "
0064                                              "if you are running Windows this error may be normal.";
0065         }
0066 
0067         // fall back
0068 
0069         qCWarning(DIGIKAM_DATABASE_LOG) << "Unable to identify a path with Solid. Adding the location with path only.";
0070 
0071         ChangingDB changing(d);
0072         CoreDbAccess().db()->addAlbumRoot(CollectionLocation::VolumeHardWired,
0073                                           d->volumeIdentifier(path), QLatin1String("/"), label);
0074     }
0075 
0076     // Do not Q_EMIT the locationAdded signal here, it is done in updateLocations()
0077 
0078     updateLocations();
0079 
0080     return locationForPath(path);
0081 }
0082 
0083 CollectionLocation CollectionManager::addNetworkLocation(const QUrl& fileUrl, const QString& label)
0084 {
0085     qCDebug(DIGIKAM_DATABASE_LOG) << "addLocation" << fileUrl;
0086     QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile();
0087 
0088     if (!locationForPath(path).isNull())
0089     {
0090         return CollectionLocation();
0091     }
0092 
0093     ChangingDB changing(d);
0094     CoreDbAccess().db()->addAlbumRoot(CollectionLocation::Network,
0095                                       d->networkShareIdentifier(QStringList() << path),
0096                                       QLatin1String("/"), label);
0097 
0098     // Do not Q_EMIT the locationAdded signal here, it is done in updateLocations()
0099 
0100     updateLocations();
0101 
0102     return locationForPath(path);
0103 }
0104 
0105 CollectionLocation CollectionManager::refreshLocation(const CollectionLocation& location, int newType,
0106                                                       const QStringList& pathList, const QString& label)
0107 {
0108     QUrl fileUrl = QUrl::fromLocalFile(pathList.first());
0109     qCDebug(DIGIKAM_DATABASE_LOG) << "refreshLocation" << fileUrl;
0110     QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile();
0111 
0112     if (location.isNull())
0113     {
0114         return CollectionLocation();
0115     }
0116 
0117     AlbumRootLocation* albumLoc = nullptr;
0118 
0119     {
0120         QReadLocker readLocker(&d->lock);
0121 
0122         albumLoc = d->locations.value(location.id());
0123 
0124         if (!albumLoc)
0125         {
0126             return CollectionLocation();
0127         }
0128     }
0129 
0130     QList<SolidVolumeInfo> volumes = d->listVolumes();
0131     SolidVolumeInfo volume         = d->findVolumeForUrl(fileUrl, volumes);
0132 
0133     if (!volume.isNull() || (newType == CollectionLocation::Network))
0134     {
0135         CollectionLocation::Type type;
0136         QString specificPath;
0137         QString identifier;
0138 
0139         if      (newType == CollectionLocation::VolumeRemovable)
0140         {
0141             // volume.path has a trailing slash. We want to split in front of this.
0142 
0143             type         = CollectionLocation::VolumeRemovable;
0144             identifier   = d->volumeIdentifier(volume);
0145             specificPath = path.mid(volume.path.length() - 1);
0146         }
0147         else if (newType == CollectionLocation::Network)
0148         {
0149             type         = CollectionLocation::Network;
0150             specificPath = QLatin1String("/");
0151             identifier   = d->networkShareIdentifier(pathList);
0152         }
0153         else
0154         {
0155             type         = CollectionLocation::VolumeHardWired;
0156             identifier   = d->volumeIdentifier(volume);
0157             specificPath = path.mid(volume.path.length() - 1);
0158         }
0159 
0160         CoreDbAccess access;
0161         ChangingDB changing(d);
0162         access.db()->setAlbumRootLabel(location.id(),           label);
0163         access.db()->setAlbumRootType(location.id(),            type);
0164         access.db()->migrateAlbumRoot(location.id(),            identifier);
0165         access.db()->setAlbumRootPath(location.id(),            specificPath);
0166         access.db()->setAlbumRootCaseSensitivity(location.id(), CollectionLocation::UnknownCaseSensitivity);
0167 
0168         albumLoc->setLabel(label);
0169         albumLoc->identifier   = identifier;
0170         albumLoc->specificPath = specificPath;
0171         albumLoc->setType((CollectionLocation::Type)type);
0172         albumLoc->setCaseSensitivity(CollectionLocation::UnknownCaseSensitivity);
0173 
0174         Q_EMIT locationPropertiesChanged(*albumLoc);
0175     }
0176     else
0177     {
0178         // Empty volumes indicates that Solid is not working correctly.
0179 
0180         if (volumes.isEmpty())
0181         {
0182             qCDebug(DIGIKAM_DATABASE_LOG) << "Solid did not return any storage volumes on your system.";
0183             qCDebug(DIGIKAM_DATABASE_LOG) << "This indicates a missing implementation or a problem with your installation";
0184             qCDebug(DIGIKAM_DATABASE_LOG) << "On Linux, check that Solid and HAL are working correctly. "
0185                                              "Problems with RAID partitions have been reported, "
0186                                              "if you have RAID this error may be normal.";
0187             qCDebug(DIGIKAM_DATABASE_LOG) << "On Windows, Solid may not be fully implemented, "
0188                                              "if you are running Windows this error may be normal.";
0189         }
0190 
0191         // fall back
0192 
0193         qCWarning(DIGIKAM_DATABASE_LOG) << "Unable to identify a path with Solid. Update the location with path only.";
0194 
0195         CoreDbAccess access;
0196         ChangingDB changing(d);
0197         CollectionLocation::Type type = CollectionLocation::VolumeHardWired;
0198         access.db()->setAlbumRootLabel(location.id(),           label);
0199         access.db()->setAlbumRootType(location.id(),            type);
0200         access.db()->setAlbumRootPath(location.id(),            QLatin1String("/"));
0201         access.db()->migrateAlbumRoot(location.id(),            d->volumeIdentifier(path));
0202         access.db()->setAlbumRootCaseSensitivity(location.id(), CollectionLocation::UnknownCaseSensitivity);
0203 
0204         albumLoc->setLabel(label);
0205         albumLoc->specificPath = QLatin1String("/");
0206         albumLoc->setType((CollectionLocation::Type)type);
0207         albumLoc->identifier   = d->volumeIdentifier(path);
0208         albumLoc->setCaseSensitivity(CollectionLocation::UnknownCaseSensitivity);
0209 
0210         Q_EMIT locationPropertiesChanged(*albumLoc);
0211     }
0212 
0213     // Do not Q_EMIT the locationAdded signal here, it is done in updateLocations()
0214 
0215     updateLocations();
0216 
0217     return locationForPath(path);
0218 }
0219 
0220 CollectionManager::LocationCheckResult CollectionManager::checkLocation(const QUrl& fileUrl,
0221                                                                         QList<CollectionLocation>& assumeDeleted,
0222                                                                         QString* message,
0223                                                                         QString* iconName)
0224 {
0225     if (!fileUrl.isLocalFile())
0226     {
0227         if (message)
0228         {
0229             *message = i18n("Sorry, digiKam does not support remote URLs as collections.");
0230         }
0231 
0232         if (iconName)
0233         {
0234             *iconName = QLatin1String("dialog-error");
0235         }
0236 
0237         return LocationNotAllowed;
0238     }
0239 
0240     QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile();
0241     QDir dir(path);
0242 
0243     if (!dir.isReadable())
0244     {
0245         if (message)
0246         {
0247             *message = i18n("The selected folder does not exist or is not readable");
0248         }
0249 
0250         if (iconName)
0251         {
0252             *iconName = QLatin1String("dialog-error");
0253         }
0254 
0255         return LocationNotAllowed;
0256     }
0257 
0258     if (d->checkIfExists(path, assumeDeleted))
0259     {
0260         if (message)
0261         {
0262             *message = i18n("There is already a collection containing the folder \"%1\"", QDir::toNativeSeparators(path));
0263         }
0264 
0265         if (iconName)
0266         {
0267             *iconName = QLatin1String("dialog-error");
0268         }
0269 
0270         return LocationNotAllowed;
0271     }
0272 
0273     QList<SolidVolumeInfo> volumes = d->listVolumes();
0274     SolidVolumeInfo volume         = d->findVolumeForUrl(fileUrl, volumes);
0275 
0276     if (!volume.isNull())
0277     {
0278         if (!volume.uuid.isEmpty())
0279         {
0280             if (volume.isRemovable)
0281             {
0282                 if (message)
0283                 {
0284                     *message = i18n("The storage media can be uniquely identified.");
0285                 }
0286 
0287                 if (iconName)
0288                 {
0289                     *iconName = QLatin1String("drive-removable-media");
0290                 }
0291             }
0292             else
0293             {
0294                 if (message)
0295                 {
0296                     *message = i18n("The collection is located on your harddisk");
0297                 }
0298 
0299                 if (iconName)
0300                 {
0301                     *iconName = QLatin1String("drive-harddisk");
0302                 }
0303             }
0304 
0305             return LocationAllRight;
0306         }
0307         else if (!volume.label.isEmpty() && (volume.isOpticalDisc || volume.isRemovable))
0308         {
0309             if (volume.isOpticalDisc)
0310             {
0311                 bool hasOtherLocation = false;
0312 
0313                 Q_FOREACH (AlbumRootLocation* const otherLocation, d->locations)
0314                 {
0315                     QUrl otherUrl(otherLocation->identifier);
0316 
0317                     if ((otherUrl.scheme() == QLatin1String("volumeid")) &&
0318                         (QUrlQuery(otherUrl).queryItemValue(QLatin1String("label")) == volume.label))
0319                     {
0320                         hasOtherLocation = true;
0321                         break;
0322                     }
0323                 }
0324 
0325                 if (iconName)
0326                 {
0327                     *iconName = QLatin1String("media-optical");
0328                 }
0329 
0330                 if (hasOtherLocation)
0331                 {
0332                     if (message)
0333                     {
0334                         *message = i18n("This is a CD/DVD, which is identified by the label "
0335                                         "that you can set in your CD burning application. "
0336                                         "There is already another entry with the same label. "
0337                                         "The two will be distinguished by the files in the top directory, "
0338                                         "so please do not append files to the CD, or it will not be recognized. "
0339                                         "In the future, please set a unique label on your CDs and DVDs "
0340                                         "if you intend to use them with digiKam.");
0341                     }
0342 
0343                     return LocationHasProblems;
0344                 }
0345                 else
0346                 {
0347                     if (message)
0348                     {
0349                         *message = i18n("This is a CD/DVD. It will be identified by the label (\"%1\")"
0350                                         "that you have set in your CD burning application. "
0351                                         "If you create further CDs for use with digikam in the future, "
0352                                         "please remember to give them a unique label as well.",
0353                                         volume.label);
0354                     }
0355 
0356                     return LocationAllRight;
0357                 }
0358             }
0359             else
0360             {
0361                 // Which situation? HasProblems or AllRight?
0362 
0363                 if (message)
0364                 {
0365                     *message = i18n("This is a removable storage medium that will be identified by its label (\"%1\")",
0366                                     volume.label);
0367                 }
0368 
0369                 if (iconName)
0370                 {
0371                     *iconName = QLatin1String("drive-removable-media");
0372                 }
0373 
0374                 return LocationAllRight;
0375             }
0376         }
0377         else
0378         {
0379             if (message)
0380             {
0381                 *message = i18n("This entry will only be identified by the path where it is found on your system (\"%1\"). "
0382                                 "No more specific means of identification (UUID, label) is available.",
0383                                 QDir::toNativeSeparators(volume.path));
0384             }
0385 
0386             if (iconName)
0387             {
0388                 *iconName = QLatin1String("drive-removale-media");
0389             }
0390 
0391             return LocationHasProblems;
0392         }
0393     }
0394     else
0395     {
0396         if (message)
0397         {
0398             *message = i18n("It is not possible on your system to identify the storage medium of this path. "
0399                             "It will be added using the file path as the only identifier. "
0400                             "This will work well for your local hard disk.");
0401         }
0402 
0403         if (iconName)
0404         {
0405             *iconName = QLatin1String("folder-important");
0406         }
0407 
0408         return LocationHasProblems;
0409     }
0410 }
0411 
0412 CollectionManager::LocationCheckResult CollectionManager::checkNetworkLocation(const QUrl& fileUrl,
0413                                                                                QList<CollectionLocation>& assumeDeleted,
0414                                                                                QString* message,
0415                                                                                QString* iconName)
0416 {
0417     if (!fileUrl.isLocalFile())
0418     {
0419         if (message)
0420         {
0421             if (fileUrl.scheme() == QLatin1String("smb"))
0422             {
0423                 *message = i18n("You need to locally mount your Samba share. "
0424                                 "Sorry, digiKam does currently not support smb:// URLs. ");
0425             }
0426             else
0427             {
0428                 *message = i18n("Your network storage must be set up to be accessible "
0429                                 "as files and folders through the operating system. "
0430                                 "digiKam does not support remote URLs.");
0431             }
0432         }
0433 
0434         if (iconName)
0435         {
0436             *iconName = QLatin1String("dialog-error");
0437         }
0438 
0439         return LocationNotAllowed;
0440     }
0441 
0442     QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile();
0443 
0444     QDir dir(path);
0445 
0446     if (!dir.isReadable())
0447     {
0448         if (message)
0449         {
0450             *message = i18n("The selected folder does not exist or is not readable");
0451         }
0452 
0453         if (iconName)
0454         {
0455             *iconName = QLatin1String("dialog-error");
0456         }
0457 
0458         return LocationNotAllowed;
0459     }
0460 
0461     if (d->checkIfExists(path, assumeDeleted))
0462     {
0463         if (message)
0464         {
0465             *message = i18n("There is already a collection for a network share with the same path.");
0466         }
0467 
0468         if (iconName)
0469         {
0470             *iconName = QLatin1String("dialog-error");
0471         }
0472 
0473         return LocationNotAllowed;
0474     }
0475 
0476     if (message)
0477     {
0478         *message = i18n("The network share will be identified by the path you selected. "
0479                         "If the path is empty, the share will be considered unavailable.");
0480     }
0481 
0482     if (iconName)
0483     {
0484         *iconName = QLatin1String("network-wired-activated");
0485     }
0486 
0487     return LocationAllRight;
0488 }
0489 
0490 void CollectionManager::removeLocation(const CollectionLocation& location)
0491 {
0492     AlbumRootLocation* albumLoc = nullptr;
0493 
0494     {
0495         QReadLocker readLocker(&d->lock);
0496 
0497         albumLoc = d->locations.value(location.id());
0498 
0499         if (!albumLoc)
0500         {
0501             return;
0502         }
0503     }
0504 
0505     // Ensure that all albums are set to orphan and no images will be permanently deleted,
0506     // as would do only calling deleteAlbumRoot by a Trigger
0507 
0508     CoreDbAccess access;
0509     QList<int> albumIds = access.db()->getAlbumsOnAlbumRoot(albumLoc->id());
0510 
0511     ChangingDB changing(d);
0512     CollectionScanner scanner;
0513     CoreDbTransaction transaction(&access);
0514 
0515     scanner.safelyRemoveAlbums(albumIds);
0516     access.db()->deleteAlbumRoot(albumLoc->id());
0517 
0518     // Do not Q_EMIT the locationRemoved signal here, it is done in updateLocations()
0519 
0520     updateLocations();
0521 }
0522 
0523 QList<CollectionLocation> CollectionManager::checkHardWiredLocations()
0524 {
0525     QList<CollectionLocation> disappearedLocations;
0526     QList<SolidVolumeInfo> volumes = d->listVolumes();
0527 
0528     QReadLocker readLocker(&d->lock);
0529 
0530     Q_FOREACH (AlbumRootLocation* const location, d->locations)
0531     {
0532         // Hardwired and unavailable?
0533 
0534         if ((location->type()   == CollectionLocation::VolumeHardWired) &&
0535             (location->status() == CollectionLocation::LocationUnavailable))
0536         {
0537             disappearedLocations << *location;
0538         }
0539     }
0540 
0541     return disappearedLocations;
0542 }
0543 
0544 void CollectionManager::migrationCandidates(const CollectionLocation& location,
0545                                             QString* const description,
0546                                             QStringList* const candidateIdentifiers,
0547                                             QStringList* const candidateDescriptions)
0548 {
0549     description->clear();
0550     candidateIdentifiers->clear();
0551     candidateDescriptions->clear();
0552 
0553     AlbumRootLocation* albumLoc = nullptr;
0554 
0555     {
0556         QReadLocker readLocker(&d->lock);
0557 
0558         albumLoc = d->locations.value(location.id());
0559 
0560         if (!albumLoc)
0561         {
0562             return;
0563         }
0564     }
0565 
0566     QList<SolidVolumeInfo> volumes = d->listVolumes();
0567     *description                   = d->technicalDescription(albumLoc);
0568 
0569     // Find possible new volumes where the specific path is found.
0570 
0571     Q_FOREACH (const SolidVolumeInfo& info, volumes)
0572     {
0573         if (info.isMounted && !info.path.isEmpty())
0574         {
0575             QDir dir(info.path + albumLoc->specificPath);
0576 
0577             if (dir.exists())
0578             {
0579                 *candidateIdentifiers  << d->volumeIdentifier(info);
0580                 *candidateDescriptions << dir.absolutePath();
0581             }
0582         }
0583     }
0584 }
0585 
0586 void CollectionManager::migrateToVolume(const CollectionLocation& location, const QString& identifier)
0587 {
0588     AlbumRootLocation* albumLoc = nullptr;
0589 
0590     {
0591         QReadLocker readLocker(&d->lock);
0592 
0593         albumLoc = d->locations.value(location.id());
0594 
0595         if (!albumLoc)
0596         {
0597             return;
0598         }
0599     }
0600 
0601     // update db
0602 
0603     ChangingDB db(d);
0604     CoreDbAccess().db()->migrateAlbumRoot(albumLoc->id(), identifier);
0605 
0606     // update local structure
0607 
0608     albumLoc->identifier = identifier;
0609 
0610     updateLocations();
0611 }
0612 
0613 void CollectionManager::setLabel(const CollectionLocation& location, const QString& label)
0614 {
0615     AlbumRootLocation* albumLoc = nullptr;
0616 
0617     {
0618         QReadLocker readLocker(&d->lock);
0619 
0620         albumLoc = d->locations.value(location.id());
0621 
0622         if (!albumLoc)
0623         {
0624             return;
0625         }
0626     }
0627 
0628     // update db
0629 
0630     ChangingDB db(d);
0631     CoreDbAccess().db()->setAlbumRootLabel(albumLoc->id(), label);
0632 
0633     // update local structure
0634 
0635     albumLoc->setLabel(label);
0636 
0637     Q_EMIT locationPropertiesChanged(*albumLoc);
0638 }
0639 
0640 void CollectionManager::changeType(const CollectionLocation& location, int type)
0641 {
0642     AlbumRootLocation* albumLoc = nullptr;
0643 
0644     {
0645         QReadLocker readLocker(&d->lock);
0646 
0647         albumLoc = d->locations.value(location.id());
0648 
0649         if (!albumLoc)
0650         {
0651             return;
0652         }
0653     }
0654 
0655     // update db
0656 
0657     ChangingDB db(d);
0658     CoreDbAccess().db()->setAlbumRootType(albumLoc->id(), (CollectionLocation::Type)type);
0659 
0660     // update local structure
0661 
0662     albumLoc->setType((CollectionLocation::Type)type);
0663 
0664     Q_EMIT locationPropertiesChanged(*albumLoc);
0665 }
0666 
0667 QList<CollectionLocation> CollectionManager::allLocations()
0668 {
0669     QReadLocker readLocker(&d->lock);
0670 
0671     QList<CollectionLocation> list;
0672 
0673     Q_FOREACH (AlbumRootLocation* const location, d->locations)
0674     {
0675         list << *location;
0676     }
0677 
0678     return list;
0679 }
0680 
0681 QList<CollectionLocation> CollectionManager::allAvailableLocations()
0682 {
0683     QReadLocker readLocker(&d->lock);
0684 
0685     QList<CollectionLocation> list;
0686 
0687     Q_FOREACH (AlbumRootLocation* const location, d->locations)
0688     {
0689         if (location->status() == CollectionLocation::LocationAvailable)
0690         {
0691             list << *location;
0692         }
0693     }
0694 
0695     return list;
0696 }
0697 
0698 CollectionLocation CollectionManager::locationForAlbumRootId(int id)
0699 {
0700     QReadLocker readLocker(&d->lock);
0701 
0702     AlbumRootLocation* const location = d->locations.value(id);
0703 
0704     if (location)
0705     {
0706         return *location;
0707     }
0708 
0709     return CollectionLocation();
0710 }
0711 
0712 CollectionLocation CollectionManager::locationForAlbumRoot(const QUrl& fileUrl)
0713 {
0714     return locationForAlbumRootPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile());
0715 }
0716 
0717 CollectionLocation CollectionManager::locationForAlbumRootPath(const QString& albumRootPath)
0718 {
0719     // This function is used when an album is created or an external scan is
0720     // initiated by the AlbumWatcher. We check if there is an entry because
0721     // the mount path of a network share may not be available.
0722 
0723     if (!QDirIterator(albumRootPath, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot).hasNext())
0724     {
0725         qCWarning(DIGIKAM_DATABASE_LOG) << "Album root path not exist" << albumRootPath;
0726         qCWarning(DIGIKAM_DATABASE_LOG) << "Drive or network connection broken?";
0727 
0728         updateLocations();
0729     }
0730 
0731     QReadLocker readLocker(&d->lock);
0732 
0733     Q_FOREACH (AlbumRootLocation* const location, d->locations)
0734     {
0735         if (location->albumRootPath() == albumRootPath)
0736         {   // cppcheck-suppress useStlAlgorithm
0737             return *location;
0738         }
0739     }
0740 
0741     return CollectionLocation();
0742 }
0743 
0744 CollectionLocation CollectionManager::locationForUrl(const QUrl& fileUrl)
0745 {
0746     return locationForPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile());
0747 }
0748 
0749 CollectionLocation CollectionManager::locationForPath(const QString& givenPath)
0750 {
0751     QReadLocker readLocker(&d->lock);
0752 
0753     Q_FOREACH (AlbumRootLocation* const location, d->locations)
0754     {
0755         QString rootPath = location->albumRootPath();
0756         QString filePath = QDir::fromNativeSeparators(givenPath);
0757 
0758         if (!rootPath.isEmpty() && filePath.startsWith(rootPath))
0759         {
0760             // see also bug #221155 for extra checks
0761 
0762             if ((filePath == rootPath) || filePath.startsWith(rootPath + QLatin1Char('/')))
0763             {
0764                 return *location;
0765             }
0766         }
0767     }
0768 
0769     return CollectionLocation();
0770 }
0771 
0772 void CollectionManager::updateLocations()
0773 {
0774     QMap<int, AlbumRootLocation*> newLocations;
0775     QMap<int, AlbumRootLocation*> oldLocations;
0776     QList<CollectionLocation::Status> oldStatus;
0777 
0778     // read information from database
0779 
0780     QList<AlbumRootInfo> infos = CoreDbAccess().db()->getAlbumRoots();
0781 
0782     // synchronize map with database
0783 
0784     {
0785         QReadLocker locker(&d->lock);
0786         oldLocations = d->locations;
0787     }
0788 
0789     Q_FOREACH (const AlbumRootInfo& info, infos)
0790     {
0791         if (oldLocations.contains(info.id))
0792         {
0793             newLocations[info.id] = oldLocations.value(info.id);
0794             oldLocations.remove(info.id);
0795         }
0796         else
0797         {
0798             newLocations[info.id] = new AlbumRootLocation(info);
0799         }
0800     }
0801 
0802     // update status with current access state,
0803     // store old status in QList oldStatus
0804 
0805     // get information from Solid
0806 
0807     QList<SolidVolumeInfo> volumes = d->listVolumes();
0808 
0809     Q_FOREACH (AlbumRootLocation* const location, newLocations)
0810     {
0811         oldStatus << location->status();
0812         bool available = false;
0813         QString absolutePath;
0814 
0815         if (location->type() == CollectionLocation::Network)
0816         {
0817             Q_FOREACH (const QString& path, d->networkShareMountPathsFromIdentifier(location))
0818             {
0819                 absolutePath      = path;
0820                 QUrl url(location->identifier);
0821                 QString uuidValue = d->getCollectionUUID(path);
0822                 QString queryItem = QUrlQuery(url).queryItemValue(QLatin1String("fileuuid"));
0823 
0824                 if      (!queryItem.isNull() && (queryItem == uuidValue))
0825                 {
0826                     available = true;
0827                 }
0828                 else if (queryItem.isNull())
0829                 {
0830                     QFileInfo fileInfo(path);
0831                     available = (fileInfo.isReadable() &&
0832                                  QDirIterator(path, QDir::Dirs    |
0833                                                     QDir::Files   |
0834                                                     QDir::NoDotAndDotDot).hasNext());
0835                 }
0836 
0837                 if (available)
0838                 {
0839                     break;
0840                 }
0841             }
0842         }
0843         else
0844         {
0845             SolidVolumeInfo info = d->findVolumeForLocation(location, volumes);
0846 
0847             if (!info.isNull())
0848             {
0849                 QString volumePath = info.path;
0850 
0851                 // volume.path has a trailing slash (and this is good)
0852                 // but specific path has a leading slash, so remove it
0853 
0854                 volumePath.chop(1);
0855 
0856                 // volumePath is the mount point of the volume;
0857                 // specific path is the path on the file system of the volume.
0858 
0859                 absolutePath = volumePath + location->specificPath;
0860                 available    = (info.isMounted && QFileInfo::exists(absolutePath));
0861             }
0862             else
0863             {
0864                 QString path = d->pathFromIdentifier(location);
0865 
0866                 if (!path.isNull())
0867                 {
0868                     available    = true;
0869 
0870                     // Here we have the absolute path as definition of the volume.
0871                     // specificPath is "/" as per convention, but ignored,
0872                     // absolute path shall not have a trailing slash.
0873 
0874                     absolutePath = path;
0875                 }
0876             }
0877         }
0878 
0879         // set values in location
0880         // Don't touch location->status, do not interfere with "hidden" setting
0881 
0882         location->available = available;
0883         location->setAbsolutePath(absolutePath);
0884 
0885         if (available)
0886         {
0887             if (d->checkCollectionUUID(location, absolutePath))
0888             {
0889                 ChangingDB changing(d);
0890                 CoreDbAccess().db()->migrateAlbumRoot(location->id(), location->identifier);
0891             }
0892         }
0893 
0894         if (available && (location->caseSensitivity() == CollectionLocation::UnknownCaseSensitivity))
0895         {
0896             QFileInfo writeInfo(absolutePath);
0897 
0898             if (writeInfo.isWritable())
0899             {
0900                 SafeTemporaryFile* const temp = new SafeTemporaryFile(absolutePath +
0901                                                                       QLatin1String("/CaseSensitivity-XXXXXX-Test"));
0902                 temp->setAutoRemove(false);
0903                 temp->open();
0904                 QFileInfo tempInfo(temp->safeFilePath());
0905                 QFileInfo testInfo(tempInfo.path()  +
0906                                    QLatin1Char('/') +
0907                                    tempInfo.fileName().toLower());
0908                 bool testCaseSensitivity      = testInfo.exists();
0909                 delete temp;
0910                 QFile::remove(tempInfo.filePath());
0911 
0912                 if (testCaseSensitivity)
0913                 {
0914                     location->setCaseSensitivity(CollectionLocation::CaseInsensitive);
0915                 }
0916                 else
0917                 {
0918                     location->setCaseSensitivity(CollectionLocation::CaseSensitive);
0919                 }
0920 
0921                 ChangingDB changing(d);
0922                 CoreDbAccess().db()->setAlbumRootCaseSensitivity(location->id(),
0923                                                                  location->caseSensitivity());
0924             }
0925         }
0926 
0927         qCDebug(DIGIKAM_DATABASE_LOG) << "Location for" << absolutePath
0928                                       << "is available:" << available
0929                                       << "=>" << "case sensitivity:"
0930                                       << location->caseSensitivity();
0931 
0932         // set the status depending on "hidden" and "available"
0933 
0934         location->setStatusFromFlags();
0935     }
0936 
0937     {
0938         QWriteLocker locker(&d->lock);
0939         d->locations = newLocations;
0940     }
0941 
0942     // Q_EMIT deleted old locations
0943 
0944     Q_FOREACH (AlbumRootLocation* const location, oldLocations)
0945     {
0946         CollectionLocation::Status statusOld = location->status();
0947         location->setStatus(CollectionLocation::LocationDeleted);
0948 
0949         Q_EMIT locationStatusChanged(*location, statusOld);
0950 
0951         delete location;
0952     }
0953 
0954     // Q_EMIT status changes (and new locations)
0955 
0956     int i = 0;
0957 
0958     Q_FOREACH (AlbumRootLocation* const location, newLocations)
0959     {
0960         if (oldStatus.at(i) != location->status())
0961         {
0962             Q_EMIT locationStatusChanged(*location, oldStatus.at(i));
0963         }
0964 
0965         ++i;
0966     }
0967 }
0968 
0969 } // namespace Digikam