File indexing completed on 2025-01-19 03:53:04

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2021-03-20
0007  * Description : a tool to export images to iNaturalist web service
0008  *
0009  * SPDX-FileCopyrightText: 2021-2022 by Joerg Lohse <joergmlpts at gmail dot com>
0010  * SPDX-FileCopyrightText: 2005-2009 by Vardhman Jain <vardhman at gmail dot com>
0011  * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  * SPDX-FileCopyrightText: 2017-2019 by Maik Qualmann <metzpinguin at gmail dot com>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #ifndef DIGIKAM_INAT_TALKER_H
0019 #define DIGIKAM_INAT_TALKER_H
0020 
0021 // Qt includes
0022 
0023 #include <QUrl>
0024 #include <QList>
0025 #include <QPair>
0026 #include <QString>
0027 #include <QObject>
0028 #include <QNetworkReply>
0029 #include <QNetworkCookie>
0030 
0031 // Local includes
0032 
0033 #include "dinfointerface.h"
0034 #include "ditemslist.h"
0035 #include "inattaxon.h"
0036 #include "inatscore.h"
0037 
0038 class QProgressDialog;
0039 
0040 using namespace Digikam;
0041 
0042 namespace DigikamGenericINatPlugin
0043 {
0044 
0045 class INatPhotoInfo;
0046 class INatPhotoSet;
0047 
0048 class AuthRequest;
0049 class ApiTokenRequest;
0050 
0051 typedef QPair<QString, QList<Taxon> > AutoCompletions;
0052 
0053 class INatTalker : public QObject
0054 {
0055     Q_OBJECT
0056 
0057 public:
0058 
0059     /**
0060      * This struct contains all the data needed for photo uploads.
0061      */
0062     struct PhotoUploadRequest
0063     {
0064         PhotoUploadRequest()
0065             : m_observationId(-1),
0066               m_totalImages  (0),
0067               m_updateIds    (false),
0068               m_rescale      (false),
0069               m_maxDim       (-1),
0070               m_quality      (-1)
0071         {
0072         }
0073 
0074         PhotoUploadRequest(const QList<QUrl>& imgs,
0075                            bool updId,
0076                            bool resize,
0077                            int mxDim,
0078                            int q, const QString& userName)
0079             : m_observationId(-1),
0080               m_totalImages  (imgs.count()),
0081               m_images       (imgs),
0082               m_user         (userName),
0083               m_updateIds    (updId),
0084               m_rescale      (resize),
0085               m_maxDim       (mxDim),
0086               m_quality      (q)
0087         {
0088         }
0089 
0090         int         m_observationId;
0091         int         m_totalImages;
0092         QList<QUrl> m_images;
0093         QString     m_apiKey;
0094         QString     m_user;
0095         bool        m_updateIds;
0096         bool        m_rescale;
0097         int         m_maxDim;
0098         int         m_quality;
0099     };
0100 
0101     /**
0102      * This struct is sent after each photo has been uploaded.
0103      */
0104     struct PhotoUploadResult
0105     {
0106         PhotoUploadResult()
0107             : m_observationPhotoId(-1),
0108               m_photoId           (-1)
0109         {
0110         }
0111 
0112         PhotoUploadResult(const PhotoUploadRequest& req, int obsPhId, int phId)
0113             : m_request           (req),
0114               m_observationPhotoId(obsPhId),
0115               m_photoId           (phId)
0116         {
0117         }
0118 
0119         PhotoUploadRequest m_request;
0120         int                m_observationPhotoId;
0121         int                m_photoId;
0122     };
0123 
0124     struct NearbyObservation
0125     {
0126 
0127         NearbyObservation()
0128             : m_observationId     (-1),
0129               m_latitude          (0.0),
0130               m_longitude         (0.0),
0131               m_distanceMeters    (-1.0),
0132               m_obscured          (false),
0133               m_referenceTaxon    (0),
0134               m_referenceLatitude (0.0),
0135               m_referenceLongitude(0.0)
0136         {
0137         }
0138 
0139         NearbyObservation(int id,
0140                           double latitude,
0141                           double longitude,
0142                           double distanceMeters,
0143                           bool obscured,
0144                           uint taxon,
0145                           double referenceLatitude,
0146                           double referenceLongitude)
0147             : m_observationId       (id),
0148               m_latitude            (latitude),
0149               m_longitude           (longitude),
0150               m_distanceMeters      (distanceMeters),
0151               m_obscured            (obscured),
0152               m_referenceTaxon      (taxon),
0153               m_referenceLatitude   (referenceLatitude),
0154               m_referenceLongitude  (referenceLongitude)
0155         {
0156         }
0157 
0158         void updateObservation(int id,
0159                                double latitude,
0160                                double longitude,
0161                                double distanceMeters)
0162         {
0163             m_observationId  = id;
0164             m_latitude       = latitude;
0165             m_longitude      = longitude;
0166             m_distanceMeters = distanceMeters;
0167         }
0168 
0169         bool isValid() const
0170         {
0171             return (m_observationId != -1);
0172         }
0173 
0174         int    m_observationId;
0175         double m_latitude;
0176         double m_longitude;
0177         double m_distanceMeters;
0178         bool   m_obscured;
0179         uint   m_referenceTaxon;
0180         double m_referenceLatitude;
0181         double m_referenceLongitude;
0182     };
0183 
0184     INatTalker(QWidget* const parent,
0185                const QString& serviceName,
0186                DInfoInterface* const iface);
0187     ~INatTalker() override;
0188 
0189     void    unLink();
0190     void    cancel();
0191     void    removeUserName(const QString& userName);
0192 
0193     /**
0194      * Are we still uploading observations or photos?
0195      */
0196     bool    stillUploading() const;
0197 
0198     /**
0199      * Returns -1 if there is no valid token; number of seconds otherwise.
0200      */
0201     int     apiTokenExpiresIn() const;
0202 
0203     /**
0204      * Try to restore a valid API token; they are good for 24 hours.
0205      */
0206     bool    restoreApiToken(const QString& username,
0207                             QList<QNetworkCookie>&,
0208                             bool emitSignal);
0209 
0210     /**
0211      * Download an image from iNaturalist servers.
0212      */
0213     void    loadUrl(const QUrl& url, int retries = 0);
0214 
0215     /**
0216      * Obtain auto completions for taxa from iNaturalist servers.
0217      */
0218     void    taxonAutoCompletions(const QString& partialName);
0219 
0220     /**
0221      * Retrieve login, name, and icon of current user.
0222      */
0223     void    userInfo(const QList<QNetworkCookie>&);
0224 
0225     /**
0226      * Get list of nearby places.
0227      */
0228     void    nearbyPlaces(double latitude, double longitude);
0229 
0230     /**
0231      * Get closest nearby observation.
0232      */
0233     void    closestObservation(uint taxon, double latitude, double longitude,
0234                                double radius_km = 10.0,
0235                                const QString& origQuery = QString());
0236 
0237     /**
0238      * Identify taxa from a given photo.
0239      */
0240     void    computerVision(const QUrl& localImage);
0241 
0242     /**
0243      * Create an iNaturalist observation; photo uploads follow.
0244      */
0245     void    createObservation(const QByteArray&, const PhotoUploadRequest&);
0246 
0247     /**
0248      * Check whether an iNaturalist observation has been created. Called
0249      * upon timeout in createObservation().
0250      */
0251     void    verifyCreateObservation(const QByteArray&,
0252                                     const PhotoUploadRequest&,
0253                                     int page, int retries);
0254 
0255     /**
0256      * Delete an observation; called when canceling uploads.
0257      */
0258     void    deleteObservation(int id, const QString& apiKey, int retries = 0);
0259 
0260     /**
0261      * Upload another photo to previously created observation.
0262      */
0263     void    uploadNextPhoto(const PhotoUploadRequest&);
0264 
0265     /**
0266      * Check whether an observation photo has been uploaded. Called
0267      * upon timeout in uploadNextPhoto().
0268      */
0269     void    verifyUploadNextPhoto(const PhotoUploadRequest&, int retries);
0270 
0271 public:
0272 
0273     QProgressDialog* m_authProgressDlg;
0274 
0275 Q_SIGNALS:
0276 
0277     void signalBusy(bool val);
0278 
0279     void signalLoadUrlSucceeded(const QUrl&, const QByteArray&);
0280     void signalTaxonAutoCompletions(const AutoCompletions&);
0281     void signalNearbyObservation(const INatTalker::NearbyObservation&);
0282     void signalComputerVisionResults(const ImageScores&);
0283     void signalNearbyPlaces(const QStringList&);
0284     void signalObservationCreated(const INatTalker::PhotoUploadRequest&);
0285     void signalObservationDeleted(int id);
0286     void signalPhotoUploaded(const INatTalker::PhotoUploadResult&);
0287 
0288     void signalLinkingSucceeded(const QString& login,
0289                                 const QString& name, const QUrl& iconUrl);
0290     void signalLinkingFailed(const QString& error);
0291 
0292 private:
0293 
0294     QString tmpFileName(const QString& path);
0295 
0296 private Q_SLOTS:
0297 
0298     void slotApiToken(const QString&, const QList<QNetworkCookie>&);
0299     void slotFinished(QNetworkReply* reply);
0300     void slotTimeout();
0301 
0302 private:
0303 
0304     class Private;
0305     Private* const d;
0306 
0307     friend class UserRequest;
0308     friend class LoadUrlRequest;
0309     friend class AutoCompletionRequest;
0310     friend class NearbyPlacesRequest;
0311     friend class NearbyObservationRequest;
0312     friend class ComputerVisionRequest;
0313 };
0314 
0315 } // namespace DigikamGenericINatPlugin
0316 
0317 #endif // DIGIKAM_INAT_TALKER_H