Ticket #12424: 0004-Replacement-Gallery-using-MythUI-storage-groups.patch

File 0004-Replacement-Gallery-using-MythUI-storage-groups.patch, 652.8 KB (added by Roger Siddons <dizygotheca@…>, 5 years ago)

Gallery

  • mythtv/libs/libmythmetadata/imagemetadata.cpp

    From 16c716d3c8f7efa3f06125179e656a09c1c35a5f Mon Sep 17 00:00:00 2001
    From: Roger Siddons <dizygotheca@ntlworld.com>
    Date: Mon, 30 Mar 2015 09:30:15 +0100
    Subject: [PATCH 4/4] Replacement Gallery using MythUI & storage groups
    
    Tested with Qt 4.8 and Qt 5.2.1
    
    Backend manages images in Storage Group and supports multiple frontends/service clients.
    
    Backend generates thumbnails for FE's and service clients. They are pre-generated in a (low-priority) background thread to improve FE
    performance. They are also generated on-demand when necessary.
    
    Thumbnails reside in <BE user>/.mythtv/tmp/Photographs. FE's cache displayed images & thumbnails in <FE user>/.mythtv/cache/themecache/<themedir>.
    Both caches are synced to the Db to prevent cache overflow.
    
    The Thumbnail View screen implements zoom by switching buttonlist widgets.
    A theme must define an "images0" buttonlist (most zoomed-out) and can optionally define others to enable zoom.
    The default theme provides 10x6, 8x4, 6x3, 4x2 views.
    
    Provides basic management functions (Create Dir, Delete, Copy/Move) which can be password-protected to thwart little fingers/prevent data loss.
    
    Provides some basic animated slideshow transitions using zoom, rotate, fade, translate and a framework for easily adding more.
    
    Displays Exif metadata and date/orientaton of videos.
    Images can be sorted by metadata timestamp.
    Photos are auto-rotated using Exif data; video auto-rotation TBD.
    
    Never writes Exif metadata to image files.
    Exif standards are poorly defined and modification may lead to corruption/data loss.
    Images may (should) have read-only permissions. Orientation overrides are stored in Db only.
    
    The menu is 'smart', in that the displayed optons depend upon the item(s) selected.
    
    Deficiencies/Known Issues.
    
    Uses current schema (>1318). However its use of the db has changed and is not compatible with old 'New Image Gallery'. Clear image database ("gallery_files" table) first. It abuses the current schema - a schema update is required to tidy it up.
    
    Does not yet support mythmediaserver.
    
    Services API is incomplete.
    
    Videos are not played in correct orientation.
    
    Slideshow zoom wobbles between 70% - 110% zoom.
    
    Not integrated with shutdown. Initial scans of large libraries will take a significant time to generate thumbnails.
    
    diff --git a/mythtv/libs/libmythmetadata/imagemetadata.cpp b/mythtv/libs/libmythmetadata/imagemetadata.cpp
    index d424b70..ed58834 100644
    a b  
    1 #include "mythcontext.h"
    2 
    31#include "imagemetadata.h"
    42
     3#include <QRegExp>
     4#include <QMap>
     5#include <QPair>
     6#include <QDateTime>
     7#include <QMutexLocker>
     8#include <QByteArray>
     9
     10// libexiv2 for Exif metadata
     11#include <exiv2/exiv2.hpp>
     12// Note: Older versions of Exiv2 don't have the exiv2.hpp include
     13// file.  Using image.hpp instead seems to work.
     14#ifdef _MSC_VER
     15#include <exiv2/src/image.hpp>
     16#else
     17#include <exiv2/image.hpp>
     18#endif
     19
     20#include <mythlogging.h>
     21
     22
     23/*!
     24 \brief Convert degree rotation into orientation code
     25 \param degrees Rotation required to show video correctly
     26 \return QString Code as per Exif spec.
     27*/
     28QString ExifOrientation::FromRotation(QString degrees)
     29{
     30    if      (degrees ==   "0") return "1";
     31    else if (degrees ==  "90") return "6";
     32    else if (degrees == "180") return "3";
     33    else if (degrees == "270") return "8";
     34    return "0";
     35}
    536
    637
    7 /** \fn     ImageMetadata::ImageMetadata()
    8  *  \brief  Constructor
    9  *  \return void
    10  */
    11 ImageMetadata::ImageMetadata()
     38/*!
     39 \brief Converts orientation code to text description
     40 \param orientation Exif code
     41 \return QString Description text
     42*/
     43QString ExifOrientation::Description(QString orientation)
    1244{
    13     m_id = 0;
    14     m_fileName = "";
    15     m_name = "";
    16     m_path = "";
    17     m_parentId = 0;
    18     m_dirCount = 0;
    19     m_fileCount = 0;
    20     m_type = 0;
    21     m_modTime = 0;
    22     m_size = 0;
    23     m_extension = "";
    24     m_angle = 0;
    25     m_orientation = 0;
    26     m_date = 0;
    27     m_zoom = 100;
    28     m_isHidden = false;
    29 
    30     m_selected = false;
    31 
    32     m_thumbPath = "";
    33     m_thumbFileNameList = new QList<QString>();
    34 
    35     // Initialize the lists to avoid assertions.
    36     for (int i = 0; i < kMaxFolderThumbnails; ++i)
    37         m_thumbFileNameList->append(QString(""));
     45    if      (orientation == "0") return tr("0 - Unset");
     46    else if (orientation == "1") return tr("1 - Normal");
     47    else if (orientation == "2") return tr("2 - Horizontally Reflected");
     48    else if (orientation == "3") return tr("3 - Rotated 180°");
     49    else if (orientation == "4") return tr("4 - Vertically Reflected");
     50    else if (orientation == "5") return tr("5 - Rotated 90°, Horizontally Reflected");
     51    else if (orientation == "6") return tr("6 - Rotated 270°");
     52    else if (orientation == "7") return tr("7 - Rotated 90°, Vertically Reflected");
     53    else if (orientation == "8") return tr("8 - Rotated 90°");
     54    return orientation;
    3855}
    3956
    4057
    41 
    42 /** \fn     ImageMetadata::~ImageMetadata()
    43  *  \brief  Destructor
    44  *  \return void
     58/*!
     59 * \brief Determines effect of applying a transform to an image
     60 * \sa http://jpegclub.org/exif_orientation.html
     61 * \details These transforms are not intuitive!
     62 * For rotations the orientation is adjusted in the opposite direction.
     63 * The transform is applied from the user perspective (as the image will be displayed),
     64 * not the current orientation. When rotated 90° horizontal/vertical flips are
     65 * reversed, and when flipped rotations are reversed.
     66 * \param im Image
     67 * \param transform Rotation/flip
     68 * \return int New orientation after applying transform
    4569 */
    46 ImageMetadata::~ImageMetadata()
     70int ExifOrientation::Transformed(int orientation, int transform)
    4771{
    48     if (m_thumbFileNameList)
    49         delete m_thumbFileNameList;
    50 }
     72    switch (orientation)
     73    {
     74    case 0: // The image has no orientation info
     75    case 1: // The image is in its original state
     76        switch (transform)
     77        {
     78        case kRotateCW:       return 6;
     79        case kRotateCCW:      return 8;
     80        case kFlipHorizontal: return 2;
     81        case kFlipVertical:   return 4;
     82        default:              return orientation;
     83        }
    5184
     85    case 2: // The image is horizontally flipped
     86        switch (transform)
     87        {
     88        case kRotateCW:       return 7;
     89        case kRotateCCW:      return 5;
     90        case kFlipHorizontal: return 1;
     91        case kFlipVertical:   return 3;
     92        default:              return orientation;
     93        }
    5294
     95    case 3: // The image is rotated 180°
     96        switch (transform)
     97        {
     98        case kRotateCW:       return 8;
     99        case kRotateCCW:      return 6;
     100        case kFlipHorizontal: return 4;
     101        case kFlipVertical:   return 2;
     102        default:              return orientation;
     103        }
    53104
    54 /** \fn     ImageMetadata::SetAngle(int)
    55  *  \brief  Sets the angle within the allowed range
    56  *  \param  angle The angle that shall be saved
    57  *  \return void
    58  */
    59 void ImageMetadata::SetAngle(int angle)
    60 {
    61     m_angle += angle;
     105    case 4: // The image is vertically flipped
     106        switch (transform)
     107        {
     108        case kRotateCW:       return 5;
     109        case kRotateCCW:      return 7;
     110        case kFlipHorizontal: return 3;
     111        case kFlipVertical:   return 1;
     112        default:              return orientation;
     113        }
    62114
    63     if (m_angle >= 360)
    64         m_angle -= 360;
     115    case 5: // The image is transposed (rotated 90° CW flipped horizontally)
     116        switch (transform)
     117        {
     118        case kRotateCW:       return 2;
     119        case kRotateCCW:      return 4;
     120        case kFlipHorizontal: return 6;
     121        case kFlipVertical:   return 8;
     122        default:              return orientation;
     123        }
    65124
    66     if (m_angle < 0)
    67         m_angle += 360;
    68 }
     125    case 6: // The image is rotated 90° CCW
     126        switch (transform)
     127        {
     128        case kRotateCW:       return 3;
     129        case kRotateCCW:      return 1;
     130        case kFlipHorizontal: return 5;
     131        case kFlipVertical:   return 7;
     132        default:              return orientation;
     133        }
     134
     135    case 7: // The image is transversed  (rotated 90° CW and flipped
     136        // vertically)
     137        switch (transform)
     138        {
     139        case kRotateCW:       return 4;
     140        case kRotateCCW:      return 2;
     141        case kFlipHorizontal: return 8;
     142        case kFlipVertical:   return 6;
     143        default:              return orientation;
     144        }
    69145
     146    case 8: // The image is rotated 90° CW
     147        switch (transform)
     148        {
     149        case kRotateCW:       return 1;
     150        case kRotateCCW:      return 3;
     151        case kFlipHorizontal: return 7;
     152        case kFlipVertical:   return 5;
     153        default:              return orientation;
     154        }
     155
     156    default: return orientation;
     157    }
     158}
    70159
    71160
    72 /** \fn     ImageMetadata::SetZoom(int)
    73  *  \brief  Sets the zoom within the allowed range
    74  *  \param  zoom The zoom value that shall be saved
    75  *  \return void
    76  */
    77 void ImageMetadata::SetZoom(int zoom, bool replace)
     161/*!
     162 \brief Sets orientation, datestamp & comment from file metadata
     163 \details Reads Exif for pictures, metadata tags from FFMPEG for videos
     164 \param im The image item to set
     165 \return bool True if metadata found
     166*/
     167bool ImageMetaData::PopulateMetaValues(ImageItem *im)
    78168{
    79     if (replace)
     169    QString absPath = ImageSg::getInstance()->GetFilePath(im);
     170    TagMap tags;
     171
     172    // Only require orientation, date, comment
     173    QPair<QString, QString> toBeFilled = qMakePair(QString(), QString());
     174    tags.insert(EXIF_TAG_ORIENTATION, toBeFilled);
     175    tags.insert(EXIF_TAG_DATETIME, toBeFilled);
     176    tags.insert(EXIF_TAG_USERCOMMENT, toBeFilled);
     177    tags.insert(EXIF_TAG_IMAGEDESCRIPTION, toBeFilled);
     178
     179    bool ok = false;
     180    if (im->m_type == kImageFile)
    80181    {
    81         m_zoom = zoom;
    82         return;
     182        ok = ReadExifTags(absPath, tags);
     183    }
     184    else if (im->m_type == kVideoFile)
     185    {
     186        ok = ReadVideoTags(absPath, tags);
    83187    }
    84188
    85     m_zoom += zoom;
    86 
    87     if (m_zoom > 300)
    88         m_zoom = 300;
     189    if (ok)
     190    {
     191        // Extract orientation
     192        if (tags.contains(EXIF_TAG_ORIENTATION))
     193        {
     194            QString orient = tags.value(EXIF_TAG_ORIENTATION).first;
     195            bool valid;
     196            int orientation = orient.toInt(&valid);
     197            im->m_orientation = (valid ? orientation : 0);
     198        }
     199        else
     200        {
     201            im->m_orientation = 0;
     202            LOG(VB_FILE, LOG_DEBUG,
     203                QString("Image: No Orientation metadata in %1").arg(im->m_name));
     204        }
     205
     206        // Extract Datetime
     207        if (tags.contains(EXIF_TAG_DATETIME))
     208        {
     209            QString date = tags.value(EXIF_TAG_DATETIME).first;
     210            // Exif time has no timezone
     211            QDateTime dateTime = QDateTime::fromString(date, "yyyy:MM:dd hh:mm:ss");
     212            if (dateTime.isValid())
     213                im->m_date = dateTime.toTime_t();
     214        }
     215        else
     216        {
     217            im->m_date = 0;
     218            LOG(VB_FILE, LOG_DEBUG,
     219                QString("Image: No DateStamp metadata in %1").arg(im->m_name));
     220        }
     221
     222        // Extract User Comment or else Image Description
     223        QString comment = "";
     224        if (tags.contains(EXIF_TAG_USERCOMMENT))
     225        {
     226            comment = tags.value(EXIF_TAG_USERCOMMENT).first;
     227        }
     228        else if (tags.contains(EXIF_TAG_IMAGEDESCRIPTION))
     229        {
     230            comment = tags.value(EXIF_TAG_IMAGEDESCRIPTION).first;
     231        }
     232        else
     233        {
     234            LOG(VB_FILE, LOG_DEBUG, QString("Image: No Comment metadata in %1")
     235                .arg(im->m_name));
     236        }
     237        im->m_comment = comment.simplified();
     238    }
    89239
    90     if (m_zoom < 20)
    91         m_zoom = 20;
     240    return ok;
    92241}
    93242
    94243
    95 
    96 /** \fn     ImageMetadata::GetOrientation()
    97  *  \brief  Gets the orientation of the image (rotated, vertically and/or
    98  *          horizontally flipped) depending on the old state.
    99  *  \return The new orientation
    100  */
    101 int ImageMetadata::GetOrientation()
     244/*!
     245 \brief Reads all metadata for an image
     246 \param im The image
     247 \param tags Map of metadata tags. Map values = Pair< camera value, camera tag label >
     248For pictures: key = Exif standard tag name; for videos: key = Exif tag name for
     249orientation, date & comment only and an arbitrary, unique int for all other tags.
     250 \return bool True if metadata exists & could be read
     251*/
     252bool ImageMetaData::GetMetaData(ImageItem *im, TagMap &tags)
    102253{
    103     return m_orientation;
     254    QString absPath = ImageSg::getInstance()->GetFilePath(im);
     255
     256    if (absPath.isEmpty())
     257        return false;
     258
     259    //
     260    bool ok = false;
     261    if (im->m_type == kImageFile)
     262    {
     263        ok = ReadExifTags(absPath, tags);
     264    }
     265    else if (im->m_type == kVideoFile)
     266    {
     267        ok = ReadVideoTags(absPath, tags);
     268    }
     269
     270    if (ok && tags.contains(EXIF_TAG_ORIENTATION))
     271    {
     272        TagPair val = tags.value(EXIF_TAG_ORIENTATION);
     273        tags.insert(EXIF_TAG_ORIENTATION,
     274                    qMakePair(ExifOrientation::Description(val.first), val.second));
     275    }
     276    return ok;
    104277}
    105278
    106279
     280/*!
     281 \brief Get Exif tags for an image
     282 \param filePath Image file
     283 \param exif Map of exif tags & values requested and/or returned.
     284 For each key the corresponding exif value is populated.
     285 If empty it is populated with all exif key/values from the image.
     286 \return bool False on exif error
     287*/
     288bool ImageMetaData::ReadExifTags(QString filePath, TagMap &tags)
     289{
     290    try
     291    {
     292        Exiv2::Image::AutoPtr image =
     293            Exiv2::ImageFactory::open(filePath.toLocal8Bit().constData());
     294
     295        if (!image.get())
     296        {
     297            LOG(VB_GENERAL, LOG_ERR,
     298                QString("Image: Exiv2 error: Could not open file %1").arg(
     299                    filePath));
     300            return false;
     301        }
     302
     303        image->readMetadata();
     304        Exiv2::ExifData &exifData = image->exifData();
     305
     306        if (exifData.empty())
     307        {
     308            LOG(VB_FILE, LOG_NOTICE,
     309                QString("Image: Exiv2 error: No exif data for file %1").arg(filePath));
     310            return false;
     311        }
     312
     313        if (tags.isEmpty())
     314        {
     315            // No specific tags requested - extract all exif data
     316            LOG(VB_FILE, LOG_DEBUG,
     317                QString("Image: Found %1 tag(s) for file %2")
     318                .arg(exifData.count())
     319                .arg(filePath));
     320
     321            Exiv2::ExifData::const_iterator i;
     322            for (i = exifData.begin(); i != exifData.end(); ++i)
     323            {
     324                QString label = QString::fromStdString(i->tagLabel());
     325
     326                // Ignore empty labels
     327                if (!label.isEmpty())
     328                {
     329                    QString key   = QString::fromStdString(i->key());
     330                    std::string rawValue = i->value().toString();
     331                    QString value;
     332
     333                    if (key == EXIF_TAG_USERCOMMENT)
     334                    {
     335                        // Decode charset
     336                        Exiv2::CommentValue comVal = Exiv2::CommentValue(rawValue);
     337                        value = QString::fromStdString(comVal.comment());
     338                    }
     339                    else
     340                        value = QString::fromStdString(rawValue);
     341
     342                    // Remove control chars from malformed exif values.
     343                    // They can pervert the myth message response mechanism
     344                    value.replace(QRegExp("[\\0000-\\0037]"), "");
     345
     346#if 0
     347                    LOG(VB_FILE, LOG_DEBUG,
     348                        QString("Image: Exif %1/\"%2\" (Type %3) : %4")
     349                        .arg(key, label, i->typeName(), value));
     350#endif
     351                    tags.insert(key, qMakePair(value, label));
     352                }
     353            }
     354        }
     355        else
     356        {
     357            // Extract requested tags only
     358            QMap<QString, QPair<QString, QString> >::iterator it;
     359            for (it = tags.begin(); it != tags.end(); ++it)
     360            {
     361                Exiv2::ExifKey            key =
     362                        Exiv2::ExifKey(it.key().toLocal8Bit().constData());
     363                Exiv2::ExifData::iterator exifIt = exifData.findKey(key);
     364
     365                if (exifIt != exifData.end())
     366                {
     367                    QString value;
     368                    std::string rawValue = exifIt->value().toString();
     369                    if (key.key() == EXIF_TAG_USERCOMMENT)
     370                    {
     371                        // Decode charset
     372                        Exiv2::CommentValue comVal = Exiv2::CommentValue(rawValue);
     373                        value = QString::fromStdString(comVal.comment());
     374                    }
     375                    else
     376                        value = QString::fromStdString(rawValue);
     377
     378                    it.value() = qMakePair(value, QString());
     379                }
     380            }
     381        }
     382        return true;
     383    }
     384    catch (Exiv2::Error &e)
     385    {
     386        LOG(VB_GENERAL, LOG_ERR,
     387            QString("Image: Exiv2 exception %1").arg(e.what()));
     388    }
     389    return false;
     390}
    107391
    108 /** \fn     ImageMetadata::SetOrientation(int)
    109  *  \brief  Sets the orientation of the image (rotated, vertically and/or
    110  *          horizontally flipped) depending on the old state.
    111  *  \param  orientation The orientation value that shall be set
    112  *  \return void
    113  */
    114 void ImageMetadata::SetOrientation(int orientation, bool replace = false)
     392
     393/*!
     394 \brief Extract metadata tags from FFMPEG dict
     395 \param[in,out] tags Extracted tags
     396 \param[in,out] arbKey Used as a map key for tags other than Orientation or Date
     397 \param dict    FFMPEG metdata dict containing video metadata
     398*/
     399void ImageMetaData::ExtractVideoTags(TagMap &tags, int &arbKey, AVDictionary *dict)
    115400{
    116     if (replace)
     401    AVDictionaryEntry *avTag = av_dict_get(dict, "\0", NULL, AV_DICT_IGNORE_SUFFIX);
     402    while (avTag)
    117403    {
    118         m_orientation = orientation;
    119         return;
     404        QString key;
     405        QString label = QString(avTag->key);
     406        QString value = QString::fromUtf8(avTag->value);
     407
     408        if (label == "rotate")
     409        {
     410            // Flag orientation & convert to Exif code
     411            key   = EXIF_TAG_ORIENTATION;
     412            label = "Orientation";
     413            value = ExifOrientation::FromRotation(value);
     414        }
     415        else if (label == "creation_time")
     416        {
     417            // Flag date & convert to Exif date format "YYYY:MM:DD"
     418            key   = EXIF_TAG_DATETIME;
     419            label = "Date and Time";
     420            value.replace("-", ":");
     421        }
     422        else
     423            key = QString::number(arbKey++);
     424
     425        tags.insert(key, qMakePair(value, label));
     426#if 0
     427        LOG(VB_FILE, LOG_DEBUG,
     428            QString("Image: Video %1/\"%2\" : %3").arg(key, avTag->key, value));
     429#endif
     430        avTag = av_dict_get(dict, "\0", avTag, AV_DICT_IGNORE_SUFFIX);
    120431    }
     432}
    121433
    122     switch (m_orientation)
     434
     435/*!
     436 \brief Get metadata tags from a video file
     437 \param filePath Video file
     438 \param[in,out] tags Map of extracted tags
     439 \return bool True if metadata exists and could be read
     440*/
     441bool ImageMetaData::ReadVideoTags(QString filePath, TagMap &tags)
     442{
    123443    {
    124     case 0: // The image has no orientation saved
    125     case 1: // If the image is in its original state
    126         if (orientation == kFileRotateCW)
    127             m_orientation = 8;
    128         else if (orientation == kFileRotateCCW)
    129             m_orientation = 6;
    130         else if (orientation == kFileFlipHorizontal)
    131             m_orientation = 2;
    132         else if (orientation == kFileFlipVertical)
    133             m_orientation = 4;
    134         break;
     444        QMutexLocker locker(avcodeclock);
     445        av_register_all();
     446    }
    135447
    136     case 2: // The image is horizontally flipped
    137         if (orientation == kFileRotateCW)
    138             m_orientation = 7;
    139         else if (orientation == kFileRotateCCW)
    140             m_orientation = 5;
    141         else if (orientation == kFileFlipHorizontal)
    142             m_orientation = 1;
    143         else if (orientation == kFileFlipVertical)
    144             m_orientation = 3;
    145         break;
     448    AVFormatContext* p_context = NULL;
     449    AVInputFormat* p_inputformat = NULL;
     450    QByteArray local8bit = filePath.toLocal8Bit();
    146451
    147     case 3: // The image is rotated 180°
    148         if (orientation == kFileRotateCW)
    149             m_orientation = 6;
    150         else if (orientation == kFileRotateCCW)
    151             m_orientation = 8;
    152         else if (orientation == kFileFlipHorizontal)
    153             m_orientation = 4;
    154         else if (orientation == kFileFlipVertical)
    155             m_orientation = 2;
    156         break;
     452    // Open file
     453    if ((avformat_open_input(&p_context, local8bit.constData(),
     454                             p_inputformat, NULL) < 0))
     455        return false;
    157456
    158     case 4: // The image is vertically flipped
    159         if (orientation == kFileRotateCW)
    160             m_orientation = 5;
    161         else if (orientation == kFileRotateCCW)
    162             m_orientation = 7;
    163         else if (orientation == kFileFlipHorizontal)
    164             m_orientation = 3;
    165         else if (orientation == kFileFlipVertical)
    166             m_orientation = 1;
    167         break;
     457    // Locate video stream
     458    int vidStream = av_find_best_stream(p_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
     459    if (vidStream < 0)
     460        return false;
    168461
    169     case 5: // The image is transposed (rotated 90° CW flipped horizontally)
    170         if (orientation == kFileRotateCW)
    171             m_orientation = 2;
    172         else if (orientation == kFileRotateCCW)
    173             m_orientation = 4;
    174         else if (orientation == kFileFlipHorizontal)
    175             m_orientation = 8;
    176         else if (orientation == kFileFlipVertical)
    177             m_orientation = 6;
    178         break;
     462    // Cannot search tags so must extract them all
     463    // No tag classification for video so use arbitrary, unique keys
    179464
    180     case 6: // The image is rotated 90° CCW
    181         if (orientation == kFileRotateCW)
    182             m_orientation = 1;
    183         else if (orientation == kFileRotateCCW)
    184             m_orientation = 3;
    185         else if (orientation == kFileFlipHorizontal)
    186             m_orientation = 7;
    187         else if (orientation == kFileFlipVertical)
    188             m_orientation = 5;
    189         break;
    190 
    191     case 7: // The image is transversed  (rotated 90° CW and flipped vertically)
    192         if (orientation == kFileRotateCW)
    193             m_orientation = 4;
    194         else if (orientation == kFileRotateCCW)
    195             m_orientation = 2;
    196         else if (orientation == kFileFlipHorizontal)
    197             m_orientation = 6;
    198         else if (orientation == kFileFlipVertical)
    199             m_orientation = 8;
    200         break;
     465    int arbKey = 1;
     466    // Extract file tags
     467    ExtractVideoTags(tags, arbKey, p_context->metadata);
     468    // Extract video tags
     469    ExtractVideoTags(tags, arbKey, p_context->streams[vidStream]->metadata);
    201470
    202     case 8: // The image is rotated 90° CW
    203         if (orientation == kFileRotateCW)
    204             m_orientation = 3;
    205         else if (orientation == kFileRotateCCW)
    206             m_orientation = 1;
    207         else if (orientation == kFileFlipHorizontal)
    208             m_orientation = 5;
    209         else if (orientation == kFileFlipVertical)
    210             m_orientation = 7;
    211         break;
    212 
    213     default:
    214         break;
    215     }
     471    avformat_close_input(&p_context);
     472
     473    return true;
    216474}
  • mythtv/libs/libmythmetadata/imagemetadata.h

    diff --git a/mythtv/libs/libmythmetadata/imagemetadata.h b/mythtv/libs/libmythmetadata/imagemetadata.h
    index f32cb2c..42222e7 100644
    a b  
     1//! \file
     2//! \brief Reads metadata (Exif & video tags) from an image file
     3
    14#ifndef IMAGEMETADATA_H
    25#define IMAGEMETADATA_H
    36
    4 // Qt headers
    5 #include <QFileInfo>
    6 #include <QString>
    7 #include <QImage>
    8 #include <QList>
     7#include <QCoreApplication>
     8#include <QMap>
     9#include <QPair>
    910
    10 // MythTV headers
    11 #include "mythmetaexp.h"
     11#include <mythmetaexp.h>
    1212
     13// FFMPEG Metadata
     14extern "C" {
     15#include <libavformat/avformat.h>
     16}
    1317
     18#include "imageutils.h"
    1419
    15 // We need to use other names to avoid
    16 // getting coflicts with the videolist.h file
    17 enum ImageTreeNodeType {
    18     kUnknown        = 0,
    19     kBaseDirectory  = 1,
    20     kSubDirectory   = 2,
    21     kUpDirectory    = 3,
    22     kImageFile      = 4,
    23     kVideoFile      = 5
    24 };
    2520
    26 enum ImageFileOrientationState {
    27     kFileRotateCW       = 0,
    28     kFileRotateCCW      = 1,
    29     kFileFlipHorizontal = 2,
    30     kFileFlipVertical   = 3,
    31     kFileZoomIn         = 4,
    32     kFileZoomOut        = 5
    33 };
     21// Exif 2.2 standard tag names, see http://www.exiv2.org/tags.html
     22#define EXIF_TAG_ORIENTATION      "Exif.Image.Orientation"
     23#define EXIF_TAG_DATETIME         "Exif.Image.DateTime"
     24#define EXIF_TAG_IMAGEDESCRIPTION "Exif.Image.ImageDescription"
     25#define EXIF_TAG_USERCOMMENT      "Exif.Photo.UserComment"
    3426
    35 enum ImageFileSortOrder {
    36     kSortByNameAsc     = 0,
    37     kSortByNameDesc    = 1,
    38     kSortByModTimeAsc  = 2,
    39     kSortByModTimeDesc = 3,
    40     kSortByExtAsc      = 4,
    41     kSortByExtDesc     = 5,
    42     kSortBySizeAsc     = 6,
    43     kSortBySizeDesc    = 7,
    44     kSortByDateAsc     = 8,
    45     kSortByDateDesc    = 9
     27
     28enum ImageFileTransform {
     29    kResetExif      = 0, //!< Reset to Exif value
     30    kRotateCW       = 1, //!< Rotate clockwise
     31    kRotateCCW      = 2, //!< Rotate anti-clockwise
     32    kFlipHorizontal = 3, //!< Reflect about vertical axis
     33    kFlipVertical   = 4  //!< Reflect about horizontal axis
    4634};
    4735
    48 const static int kMaxFolderThumbnails = 4;
     36
     37//! Manages Exif orientation code
     38class META_PUBLIC ExifOrientation
     39{
     40    Q_DECLARE_TR_FUNCTIONS(ExifOrientation)
     41public:
     42    static QString FromRotation(QString);
     43    static QString Description(QString);
     44    static int     Transformed(int, int);
     45};
    4946
    5047
    51 class META_PUBLIC ImageMetadata
     48//! Reads metadata from image files
     49class META_PUBLIC ImageMetaData
    5250{
    5351public:
    54     ImageMetadata();
    55     ~ImageMetadata();
    56 
    57     // Database fields
    58     int         m_id;
    59     QString     m_fileName;
    60     QString     m_name;
    61     QString     m_path;
    62     int         m_parentId;
    63     int         m_dirCount;
    64     int         m_fileCount;
    65     int         m_type;
    66     int         m_modTime;
    67     int         m_size;
    68     QString     m_extension;
    69     double      m_date;
    70     int         m_isHidden;
    71 
    72     // Internal information
    73     bool        m_selected;
    74 
    75     int         GetAngle()  const   { return m_angle; }
    76     int         GetZoom()   const   { return m_zoom; }
    77     int         GetOrientation();
    78     void        SetAngle(int);
    79     void        SetZoom(int, bool);
    80     void        SetOrientation(int, bool);
    81 
    82     // Internal thumbnail information
    83     QString         m_thumbPath;
    84     QList<QString> *m_thumbFileNameList;
    85     QList<int>      m_thumbFileIdList;
     52    typedef QPair<QString, QString> TagPair;
     53    typedef QMap<QString, TagPair>  TagMap;
     54
     55    static bool PopulateMetaValues(ImageItem *);
     56    static bool GetMetaData(ImageItem *, TagMap &);
    8657
    8758private:
    88     int         m_zoom;
    89     int         m_angle;
    90     int         m_orientation;
     59    static bool ReadExifTags(QString, TagMap &);
     60    static bool ReadVideoTags(QString, TagMap &);
     61    static void ExtractVideoTags(TagMap &tags, int &arbKey, AVDictionary *dict);
    9162};
    9263
    93 Q_DECLARE_METATYPE(ImageMetadata*)
    94 
    9564#endif // IMAGEMETADATA_H
  • deleted file mythtv/libs/libmythmetadata/imagescan.cpp

    diff --git a/mythtv/libs/libmythmetadata/imagescan.cpp b/mythtv/libs/libmythmetadata/imagescan.cpp
    deleted file mode 100644
    index e16c823..0000000
    + -  
    1 // Qt headers
    2 
    3 // MythTV headers
    4 #include "mythcontext.h"
    5 #include "imagescan.h"
    6 
    7 
    8 
    9 ImageScan* ImageScan::m_instance = NULL;
    10 
    11 ImageScan::ImageScan()
    12 {
    13     m_imageScanThread = new ImageScanThread();
    14 }
    15 
    16 
    17 
    18 ImageScan::~ImageScan()
    19 {
    20     if (m_imageScanThread)
    21     {
    22         delete m_imageScanThread;
    23         m_imageScanThread = NULL;
    24     }
    25 }
    26 
    27 
    28 
    29 ImageScan* ImageScan::getInstance()
    30 {
    31     if (!m_instance)
    32         m_instance = new ImageScan();
    33 
    34     return m_instance;
    35 }
    36 
    37 
    38 
    39 void ImageScan::StartSync()
    40 {
    41     if (m_imageScanThread && !m_imageScanThread->isRunning())
    42     {
    43         m_imageScanThread->m_continue = true;
    44         m_imageScanThread->start();
    45     }
    46 }
    47 
    48 
    49 
    50 void ImageScan::StopSync()
    51 {
    52     if (m_imageScanThread && m_imageScanThread->isRunning())
    53         m_imageScanThread->m_continue = false;
    54 }
    55 
    56 
    57 
    58 bool ImageScan::SyncIsRunning()
    59 {
    60     if (m_imageScanThread)
    61         return m_imageScanThread->isRunning();
    62 
    63     return false;
    64 }
    65 
    66 
    67 
    68 int ImageScan::GetCurrent()
    69 {
    70     if (m_imageScanThread)
    71         return m_imageScanThread->m_progressCount;
    72 
    73     return 0;
    74 }
    75 
    76 
    77 
    78 int ImageScan::GetTotal()
    79 {
    80     if (m_imageScanThread)
    81         return m_imageScanThread->m_progressTotalCount;
    82 
    83     return 0;
    84 }
  • deleted file mythtv/libs/libmythmetadata/imagescan.h

    diff --git a/mythtv/libs/libmythmetadata/imagescan.h b/mythtv/libs/libmythmetadata/imagescan.h
    deleted file mode 100644
    index d10767a..0000000
    + -  
    1 #ifndef IMAGESCAN_H
    2 #define IMAGESCAN_H
    3 
    4 // Qt headers
    5 
    6 // MythTV headers
    7 #include "imagescanthread.h"
    8 #include "mythmetaexp.h"
    9 
    10 
    11 
    12 class META_PUBLIC ImageScan
    13 {
    14 public:
    15     static ImageScan*    getInstance();
    16 
    17     void StartSync();
    18     void StopSync();
    19     bool SyncIsRunning();
    20 
    21     int  GetCurrent();
    22     int  GetTotal();
    23 
    24 private:
    25     ImageScan();
    26     ~ImageScan();
    27     static ImageScan    *m_instance;
    28 
    29     ImageScanThread     *m_imageScanThread;
    30 };
    31 
    32 #endif // IMAGESCAN_H
  • new file mythtv/libs/libmythmetadata/imagescanner.cpp

    diff --git a/mythtv/libs/libmythmetadata/imagescanner.cpp b/mythtv/libs/libmythmetadata/imagescanner.cpp
    new file mode 100644
    index 0000000..8f4c339
    - +  
     1#include "imagescanner.h"
     2
     3#include <QtAlgorithms>
     4
     5#include <imagethumbs.h>
     6#include <imagemetadata.h>
     7#include <imageutils.h>
     8
     9/*!
     10 \brief  Constructor
     11*/
     12ImageScanThread::ImageScanThread() :
     13    MThread("ImageScanner"),
     14    m_db(),
     15    m_sg(ImageSg::getInstance()),
     16    m_progressCount(0),
     17    m_progressTotalCount(0),
     18    m_dir(ImageSg::getInstance()->GetImageFilters()),
     19    m_exclusions()
     20{
     21    QMutexLocker locker(&m_mutexState);
     22    m_state = kDormant;
     23}
     24
     25
     26/*!
     27 \brief  Destructor
     28*/
     29ImageScanThread::~ImageScanThread()
     30{
     31    cancel();
     32    wait();
     33}
     34
     35
     36/*!
     37 \brief  Clears the thumbnail list so that the thread can exit.
     38*/
     39void ImageScanThread::cancel()
     40{
     41    QMutexLocker locker(&m_mutexState);
     42    m_state = kInterrupt;
     43}
     44
     45
     46/*!
     47 \brief Return current scanner status
     48 \return ScannerState
     49*/
     50ScannerState ImageScanThread::GetState()
     51{
     52    QMutexLocker locker(&m_mutexState);
     53    return m_state;
     54}
     55
     56
     57/*!
     58 \brief Request scan start/stop, clear Db
     59 \param to New state
     60*/
     61void ImageScanThread::ChangeState(ScannerState to)
     62{
     63    QMutexLocker locker(&m_mutexState);
     64    m_state = to;
     65
     66    // Restart thread if not already running
     67    if (!this->isRunning())
     68        this->start();
     69}
     70
     71
     72/*!
     73 \brief Returns number of images scanned & total number to scan
     74 \return QStringList "done/total"
     75*/
     76QStringList ImageScanThread::GetProgress()
     77{
     78    QMutexLocker locker(&m_mutexProgress);
     79    return QStringList() << QString::number(m_progressCount)
     80                         << QString::number(m_progressTotalCount);
     81}
     82
     83
     84/*!
     85 \brief Notify listeners of mode & progress
     86 \param mode Mode to broadcast
     87*/
     88void ImageScanThread::BroadcastStatus(QString mode)
     89{
     90    QStringList status;
     91
     92    { // Release lock before sending message
     93        QMutexLocker locker(&m_mutexProgress);
     94
     95        status << mode
     96               << QString::number(m_progressCount)
     97               << QString::number(m_progressTotalCount);
     98    }
     99
     100    MythEvent me = MythEvent("IMAGE_SCAN_STATUS", status);
     101    gCoreContext->SendEvent(me);
     102}
     103
     104
     105/*!
     106 \brief Counts images in a dir subtree
     107 \details Ignores files/dirs that match exclusions regexp
     108 \param dir Parent of subtree
     109*/
     110void ImageScanThread::CountTree(QDir &dir)
     111{
     112    QFileInfoList files = dir.entryInfoList();
     113
     114    foreach(const QFileInfo &fileInfo, files)
     115    {
     116        if (fileInfo.isFile())
     117            ++m_progressTotalCount;
     118        else if (m_exclusions.exactMatch(fileInfo.fileName()))
     119            LOG(VB_GENERAL, LOG_INFO, QString("%1: Excluding %2")
     120                .arg(objectName(), fileInfo.filePath()));
     121        else
     122        {
     123            dir.cd(fileInfo.fileName());
     124            CountTree(dir);
     125            dir.cdUp();
     126        }
     127    }
     128}
     129
     130
     131/*!
     132 \brief Counts images in a list of subtrees
     133 \param paths List of dirs
     134*/
     135void ImageScanThread::CountFiles(QStringList paths)
     136{
     137    // Get exclusions as comma-seperated list using glob chars * and ?
     138    QString excPattern = gCoreContext->GetSetting("GalleryIgnoreFilter", "");
     139
     140    // Combine into a single regexp
     141    excPattern.replace(".", "\\."); // Preserve "."
     142    excPattern.replace("*", ".*");  // Convert glob wildcard "*"
     143    excPattern.replace("?", ".");  // Convert glob wildcard "?"
     144    excPattern.replace(",", "|");   // Convert list to OR's
     145
     146    QString pattern = QString("^(%1)$").arg(excPattern);
     147    m_exclusions = QRegExp(pattern);
     148
     149    LOG(VB_FILE, LOG_DEBUG, QString("%1: Exclude regexp is \"%2\"")
     150        .arg(objectName(), pattern));
     151
     152    QMutexLocker locker(&m_mutexProgress);
     153    m_progressCount       = 0;
     154    m_progressTotalCount  = 0;
     155
     156    // Release lock to broadcast
     157    locker.unlock();
     158    BroadcastStatus("SCANNING");
     159    locker.relock();
     160
     161    // Use global image filters
     162    QDir dir = m_dir;
     163    foreach(const QString &sgDir, paths)
     164    {
     165        dir.cd(sgDir);
     166        CountTree(dir);
     167    }
     168}
     169
     170
     171/*!
     172 \brief Synchronises database to the storage group
     173 \details Reads all dirs and files in storage group and populates database with
     174 metadata for each. Broadcasts progress events whilst scanning and initiates
     175 thumbnail generation when finished.
     176*/
     177void ImageScanThread::run()
     178{
     179    RunProlog();
     180
     181    setPriority(QThread::LowPriority);
     182
     183    QStringList removed;
     184
     185    // Scan requested ?
     186    if (m_state == kScan)
     187    {
     188        LOG(VB_GENERAL, LOG_INFO, objectName() + ": Starting scan");
     189
     190        // Known files/dirs in the Db. Objects are either;
     191        // - deleted explicitly (when matched to the filesystem),
     192        // - or passed to the Thumbnail Generator (old dirs/files that have disappeared)
     193        m_dbDirMap = new ImageMap();
     194        m_dbFileMap = new ImageMap();
     195
     196        // Scan all SG dirs
     197        QStringList paths = m_sg->GetStorageDirs();
     198
     199        CountFiles(paths);
     200
     201        // Load all available directories and files from the database so that
     202        // they can be compared against the ones on the filesystem.
     203        // Ignore root dir, which is notional
     204        m_db.ReadDbItems(*m_dbFileMap, *m_dbDirMap,
     205                           QString("file_id != %1").arg(ROOT_DB_ID));
     206
     207        // Ensure Root dir exists as first db entry and update last scan time
     208        ImageItem root;
     209        root.m_id            = ROOT_DB_ID;
     210        root.m_name          = QString("");
     211        root.m_parentId      = 0;
     212        root.m_type          = kBaseDirectory;
     213        root.m_modTime       = QDateTime::currentMSecsSinceEpoch() / 1000;
     214
     215        m_db.UpdateDbFile(&root);
     216
     217        // Now start the actual syncronization
     218        foreach(const QString &path, paths)
     219        {
     220            QString base = path;
     221            if (!base.endsWith('/'))
     222                base.append('/');
     223
     224            LOG(VB_FILE, LOG_INFO,
     225                QString("%1: Syncing from SG dir %2").arg(objectName(), path));
     226
     227            SyncFilesFromDir(path, ROOT_DB_ID, base);
     228        }
     229
     230        // Adding or updating directories has been completed.
     231        // The maps now only contain old directories & files that are not
     232        // in the filesystem anymore. Remove them from the database
     233        ImageList files = m_dbDirMap->values() + m_dbFileMap->values();
     234        m_db.RemoveFromDB(files);
     235
     236        // Cleanup thumbnails
     237        removed = ImageThumb::getInstance()->DeleteThumbs(m_dbFileMap->values(),
     238                                                          m_dbDirMap->values());
     239
     240        LOG(VB_GENERAL, LOG_INFO, objectName() + ": Finished scan");
     241
     242        // Clean up containers & contents
     243        delete m_dbFileMap;
     244        delete m_dbDirMap;
     245
     246        // Wait for 'urgent' thumbs to be generated before notifying clients.
     247        // Otherwise they'd have nothing to draw...
     248        WaitForThumbs();
     249    }
     250
     251    // Scan has completed or been interrupted. Now process any Clear request
     252    if (m_state == kClear)
     253    {
     254        LOG(VB_GENERAL, LOG_INFO, objectName() + ": Clearing Database");
     255
     256        m_db.ClearDb();
     257        ImageThumb::getInstance()->ClearAllThumbs();
     258
     259        removed = QStringList("ALL");
     260    }
     261
     262    ChangeState(kDormant);
     263
     264    // Notify scan has finished
     265    BroadcastStatus("");
     266
     267    // Notify clients of Db update
     268    gCoreContext->SendEvent(MythEvent("IMAGE_DB_CHANGED", removed));
     269
     270    RunEpilog();
     271}
     272
     273
     274/*!
     275 \brief Blocks until all urgent thumbnails have been generated. Time-outs after
     276 10 secs
     277*/
     278void ImageScanThread::WaitForThumbs()
     279{
     280    // Wait up to 10s for queue to empty.
     281    int remaining = ImageThumb::getInstance()->GetQueueSize(kScannerUrgentPriority);
     282
     283    { // Counts now represent pending thumbnails
     284        QMutexLocker locker(&m_mutexProgress);
     285        m_progressCount       = 0;
     286        m_progressTotalCount  = remaining;
     287    }
     288
     289    int timeout = 100;
     290    while (remaining > 0 && --timeout > 0)
     291    {
     292        BroadcastStatus("THUMBNAILS");
     293        msleep(1000);
     294        remaining = ImageThumb::getInstance()->GetQueueSize(kScannerUrgentPriority);
     295        QMutexLocker locker(&m_mutexProgress);
     296        m_progressCount = std::max(m_progressTotalCount - remaining, 0);
     297    }
     298    BroadcastStatus("THUMBNAILS");
     299}
     300
     301
     302/*!
     303 \brief Scans a dir subtree and updates/populates db metadata to match filesystem
     304 \details Detects all files that match image/video filters. Files/dirs that
     305 match exclusions regexp are ignored.
     306 \param path Dir that is subtree root
     307 \param parentId Db id of the dir's parent dir
     308 \param baseDirectory The storage group root dir path
     309*/
     310void ImageScanThread::SyncFilesFromDir(QString path,
     311                                       int parentId,
     312                                       QString baseDirectory)
     313{
     314    // Use global image filters
     315    QDir dir = m_dir;
     316    dir.cd(path);
     317    QFileInfoList list = dir.entryInfoList();
     318
     319    foreach(const QFileInfo &fileInfo, list)
     320    {
     321        // Ignore excluded files
     322        if (m_exclusions.exactMatch(fileInfo.fileName()))
     323            continue;
     324
     325        { // Release lock before continuing
     326            QMutexLocker locker(&m_mutexState);
     327            if (m_state != kScan)
     328            {
     329                LOG(VB_GENERAL, LOG_INFO,
     330                    QString("%1: Scan interrupted in %2").arg(objectName(), path));
     331                return;
     332            }
     333        }
     334
     335        if (fileInfo.isDir())
     336        {
     337            // Get the id. This will be new parent id
     338            // when we traverse down the current directory.
     339            int id = SyncDirectory(fileInfo, parentId, baseDirectory);
     340
     341            // Get new files within this directory
     342            QString fileName = fileInfo.absoluteFilePath();
     343            SyncFilesFromDir(fileName, id, baseDirectory);
     344        }
     345        else
     346        {
     347            SyncFile(fileInfo, parentId, baseDirectory);
     348
     349            // Update progress count
     350            { // Release lock quickly
     351                QMutexLocker locker(&m_mutexProgress);
     352                ++m_progressCount;
     353            }
     354            // report status on completion of every dir
     355            BroadcastStatus("SCANNING");
     356        }
     357    }
     358}
     359
     360
     361/*!
     362 \brief Updates/populates db for a dir
     363 \details Dir is updated if dir modified time has changed since last scan.
     364 \param fileInfo Dir info
     365 \param parentId Db id of the dir's parent dir
     366 \param baseDirectory The storage group root dir path
     367 \return int Db id of this dir in db
     368*/
     369int ImageScanThread::SyncDirectory(QFileInfo fileInfo,
     370                                   int parentId,
     371                                   QString baseDirectory)
     372{
     373    LOG(VB_FILE, LOG_DEBUG, QString("%1: Syncing directory %2")
     374        .arg(objectName(), fileInfo.absoluteFilePath()));
     375
     376    // Load all required information of the directory
     377    ImageItem *dir = LoadDirectoryData(fileInfo, parentId, baseDirectory);
     378
     379    ImageItem *dbDir = m_dbDirMap->value(dir->m_fileName);
     380    int id;
     381
     382    if (dbDir)
     383    {
     384        // The directory already exists in the db. Retain its id
     385        id = dir->m_id = dbDir->m_id;
     386
     387        // Check for change of contents
     388        if (dir->m_modTime != dbDir->m_modTime)
     389        {
     390            LOG(VB_FILE, LOG_INFO, QString("%1: Changed directory %2")
     391                .arg(objectName(), fileInfo.absoluteFilePath()));
     392            m_db.UpdateDbFile(dir);
     393        }
     394
     395        // Remove the entry from the dbList
     396        m_dbDirMap->remove(dir->m_fileName);
     397        delete dbDir;
     398    }
     399    else
     400    {
     401        LOG(VB_FILE, LOG_INFO, QString("%1: New directory %2")
     402            .arg(objectName(), fileInfo.absoluteFilePath()));
     403
     404        // The directory is not in the database list
     405        // add it to the database and get the new id. This
     406        // will be the new parent id for the subdirectories
     407        id = m_db.InsertDbDirectory(*dir);
     408    }
     409    delete dir;
     410    return id;
     411}
     412
     413
     414
     415/*!
     416 \brief Updates/populates db for an image/video file
     417 \details Image is updated if file modified time has changed since last scan.
     418Extracts orientation, date and 2 comments from exif data
     419 \param fileInfo File info
     420 \param parentId Db id of the file's parent dir
     421 \param baseDirectory The storage group root dir path
     422*/
     423void ImageScanThread::SyncFile(QFileInfo fileInfo,
     424                               int parentId,
     425                               QString baseDirectory)
     426{
     427    // Load all required information of the file
     428    ImageItem *im = LoadFileData(fileInfo, baseDirectory);
     429
     430    if (!im)
     431    {
     432        LOG(VB_FILE, LOG_DEBUG, QString("%1: Ignoring unknown file %2")
     433            .arg(objectName(), fileInfo.absoluteFilePath()));
     434        return;
     435    }
     436
     437    // get db version of this file
     438    ImageItem* oldim = m_dbFileMap->value(im->m_fileName);
     439
     440    if (oldim && oldim->m_modTime == im->m_modTime)
     441    {
     442        // File already known & hasn't changed
     443        // Remove the entry from the dbList
     444        m_dbFileMap->remove(im->m_fileName);
     445        delete oldim;
     446        delete im;
     447        return;
     448    }
     449
     450    if (oldim)
     451    {
     452        LOG(VB_FILE, LOG_INFO, QString("%1: Modified file %2")
     453            .arg(objectName(), fileInfo.absoluteFilePath()));
     454
     455        // changed images retain their existing id
     456        im->m_id = oldim->m_id;
     457
     458        // Remove the entry from the dbList
     459        m_dbFileMap->remove(oldim->m_fileName);
     460        delete oldim;
     461    }
     462    else
     463    {
     464        LOG(VB_FILE, LOG_INFO, QString("%1: New file %2")
     465            .arg(objectName(), fileInfo.absoluteFilePath()));
     466
     467        // new images will be assigned an id by the db AUTO-INCREMENT
     468        im->m_id = 0;
     469    }
     470
     471    // Set the parent.
     472    im->m_parentId = parentId;
     473
     474    // Set orientation, date, comment from file meta data
     475    ImageMetaData::PopulateMetaValues(im);
     476
     477    // Update db
     478    m_db.UpdateDbFile(im);
     479
     480    // Ensure thumbnail exists.
     481    // Do all top level images asap (they may be needed when scan finishes)
     482    // Thumb generator now owns image
     483    ImageThumbPriority thumbPriority = (parentId == ROOT_DB_ID
     484                         ? kScannerUrgentPriority : kBackgroundPriority);
     485
     486    ImageThumb::getInstance()->CreateThumbnail(im, thumbPriority);
     487}
     488
     489
     490/*!
     491 \brief Creates metadata from directory info
     492 \param fileInfo Dir info
     493 \param parentId Db id of the dir's parent dir
     494 \param baseDirectory The storage group root dir path
     495 \return ImageItem New metadata object
     496*/
     497ImageItem* ImageScanThread::LoadDirectoryData(QFileInfo fileInfo,
     498                                              int parentId,
     499                                              QString baseDirectory)
     500{
     501    ImageItem *dirIm = new ImageItem();
     502
     503    QDir dir(baseDirectory);
     504    dirIm->m_parentId    = parentId;
     505    dirIm->m_fileName    = dir.relativeFilePath(fileInfo.absoluteFilePath());
     506    dirIm->m_name        = fileInfo.fileName();
     507    dirIm->m_path        = dir.relativeFilePath(fileInfo.absolutePath());
     508    if (dirIm->m_path.isNull())
     509        dirIm->m_path = "";
     510    dirIm->m_modTime     = fileInfo.lastModified().toTime_t();
     511    dirIm->m_type        = kSubDirectory;
     512
     513    return dirIm;
     514}
     515
     516
     517/*!
     518 \brief Creates an item for an image file
     519 \param fileInfo File info
     520 \param parentId Db id of the file's parent dir
     521 \param baseDirectory The storage group root dir path
     522 \return ImageItem New metadata object
     523*/
     524ImageItem* ImageScanThread::LoadFileData(QFileInfo fileInfo,
     525                                         QString baseDirectory)
     526{
     527    QString extension = fileInfo.suffix().toLower();
     528    int type = m_sg->GetImageType(extension);
     529    if (type == kUnknown)
     530        return NULL;
     531
     532    ImageItem *image = new ImageItem();
     533
     534    QDir baseDir(baseDirectory);
     535    image->m_fileName  = baseDir.relativeFilePath(fileInfo.absoluteFilePath());
     536    image->m_name      = fileInfo.fileName();
     537    image->m_path      = baseDir.relativeFilePath(fileInfo.absolutePath());
     538    image->m_modTime   = fileInfo.lastModified().toTime_t();
     539    image->m_size      = fileInfo.size();
     540    image->m_type      = type;
     541    image->m_extension = extension;
     542    image->m_thumbPath = ImageUtils::ThumbPathOf(image);
     543
     544    return image;
     545}
     546
     547
     548ImageScan* ImageScan::m_instance = NULL;
     549
     550/*!
     551 \brief Constructor
     552*/
     553ImageScan::ImageScan()
     554{
     555    m_imageScanThread = new ImageScanThread();
     556}
     557
     558
     559/*!
     560 \brief Destructor
     561*/
     562ImageScan::~ImageScan()
     563{
     564    if (m_imageScanThread)
     565    {
     566        delete m_imageScanThread;
     567        m_imageScanThread = NULL;
     568    }
     569}
     570
     571
     572/*!
     573 \brief Get singleton
     574 \return ImageScan Scanner object
     575*/
     576ImageScan* ImageScan::getInstance()
     577{
     578    if (!m_instance)
     579        m_instance = new ImageScan();
     580
     581    return m_instance;
     582}
     583
     584
     585/*!
     586 \brief Process client requests for start scan, stop scan, clear Db and scan
     587  progress queries
     588 \param command Start, stop, clear or query
     589 \return QStringList ("ERROR", Error message) or
     590("OK", "SCANNING" | "", "done/total")
     591*/
     592QStringList ImageScan::HandleScanRequest(QStringList command)
     593{
     594    // Expects command & a single qualifier
     595    if (command.size() != 2)
     596        return QStringList("ERROR") << "Bad IMAGE_SCAN";
     597
     598    if (!m_imageScanThread)
     599        // Should never happen
     600        return QStringList("ERROR") << "Scanner is missing";
     601
     602    if (command[1] == "START")
     603    {
     604        // Must be dormant to start a scan
     605        bool valid = (m_imageScanThread->GetState() == kDormant);
     606
     607        if (valid)
     608            m_imageScanThread->ChangeState(kScan);
     609
     610        return valid ? QStringList("OK") : QStringList("ERROR") << "Scanner is busy";
     611    }
     612    else if (command[1] == "STOP")
     613    {
     614        // Must be scanning to interrupt
     615        bool valid = (m_imageScanThread->GetState() == kScan);
     616
     617        if (valid)
     618            m_imageScanThread->ChangeState(kInterrupt);
     619
     620        return valid ? QStringList("OK") : QStringList("ERROR") << "Scan not in progress";
     621    }
     622    else if (command[1] == "CLEAR")
     623    {
     624        // Must not be already clearing
     625        bool valid = (m_imageScanThread->GetState() != kClear);
     626
     627        if (valid)
     628            m_imageScanThread->ChangeState(kClear);
     629
     630        return valid ? QStringList("OK") : QStringList("ERROR") << "Clear already in progress";
     631
     632    }
     633    else if (command[1] == "QUERY")
     634    {
     635        QStringList reply;
     636        reply << "OK"
     637              << (m_imageScanThread->isRunning() ? "SCANNING" : "")
     638              << m_imageScanThread->GetProgress();
     639        return reply;
     640    }
     641    LOG(VB_GENERAL, LOG_ERR, "ImageScanner: Unknown command");
     642    return QStringList("ERROR") << "Unknown command";
     643}
  • new file mythtv/libs/libmythmetadata/imagescanner.h

    diff --git a/mythtv/libs/libmythmetadata/imagescanner.h b/mythtv/libs/libmythmetadata/imagescanner.h
    new file mode 100644
    index 0000000..72a6b8a
    - +  
     1//! \file
     2//! \brief Synchronises image database to storage group
     3//! \details Detects supported pictures and videos within storage group and populates
     4//! the image database with metadata for each, including directory structure.
     5//! After a scan completes, a background task then creates thumbnails for each new image
     6//! to improve client performance.
     7
     8#ifndef IMAGESCAN_H
     9#define IMAGESCAN_H
     10
     11#include <QFileInfo>
     12#include <QMap>
     13#include <QDir>
     14#include <QRegExp>
     15#include <QMutex>
     16
     17#include <mthread.h>
     18#include <imageutils.h>
     19
     20
     21//! \brief Current/last requested scanner state
     22//! \details Valid state transitions are:
     23//!  Scan -> Dormant : Scan requested
     24//!  Clear -> Dormant : Clear db requested
     25//!  Scan -> Interrupt -> Dormant : Scan requested, then interrupted
     26//!  Scan -> Clear -> Dormant : Clear db requested during scan
     27//!  Scan -> Interrupt -> Clear -> Dormant : Scan interrupted, then Clear Db requested
     28enum ScannerState
     29{
     30    kScan,      //!< sync is pending/in effect
     31    kInterrupt, //!< cancelled sync is pending/in effect
     32    kClear,     //!< clear db is pending/in effect
     33    kDormant    //!< doing nothing
     34};
     35
     36
     37//! Scanner worker thread
     38class META_PUBLIC ImageScanThread : public MThread
     39{
     40public:
     41    ImageScanThread();
     42    ~ImageScanThread();
     43
     44    void cancel();
     45    QStringList GetProgress();
     46    ScannerState GetState();
     47    void ChangeState(ScannerState to);
     48
     49protected:
     50    void run();
     51
     52private:
     53    ImageItem* LoadDirectoryData(QFileInfo, int, QString);
     54    ImageItem* LoadFileData(QFileInfo, QString);
     55
     56    void SyncFilesFromDir(QString, int, QString);
     57    int  SyncDirectory(QFileInfo, int, QString);
     58    void SyncFile(QFileInfo, int, QString);
     59    void WaitForThumbs();
     60    void BroadcastStatus(QString);
     61    void CountFiles(QStringList paths);
     62    void CountTree(QDir &);
     63
     64    //! The latest state from all clients.
     65    ScannerState m_state;
     66    //! Mutex protecting state
     67    QMutex m_mutexState;
     68
     69    // Global working vars
     70    ImageDbWriter  m_db;
     71    ImageSg       *m_sg;
     72
     73    //! Maps dir paths (relative to SG) to dir metadata
     74    ImageMap *m_dbDirMap;
     75    //! Maps file paths (relative to SG) to file metadata
     76    ImageMap *m_dbFileMap;
     77
     78    //! Number of images scanned
     79    int  m_progressCount;
     80    //! Total number of images to scan
     81    int  m_progressTotalCount;
     82    //! Progress counts mutex
     83    QMutex m_mutexProgress;
     84
     85    //! Global working dir for file detection
     86    QDir m_dir;
     87    //! Pattern of dir names to ignore whilst scanning
     88    QRegExp m_exclusions;
     89};
     90
     91
     92//! Synchronises database to the filesystem
     93class META_PUBLIC ImageScan
     94{
     95public:
     96    static ImageScan*    getInstance();
     97
     98    QStringList HandleScanRequest(QStringList);
     99
     100private:
     101    ImageScan();
     102    ~ImageScan();
     103
     104    //! Scanner singleton
     105    static ImageScan    *m_instance;
     106    //! Internal thread
     107    ImageScanThread     *m_imageScanThread;
     108};
     109
     110#endif // IMAGESCAN_H
  • deleted file mythtv/libs/libmythmetadata/imagescanthread.cpp

    diff --git a/mythtv/libs/libmythmetadata/imagescanthread.cpp b/mythtv/libs/libmythmetadata/imagescanthread.cpp
    deleted file mode 100644
    index 7a30f8b..0000000
    + -  
    1 // Qt headers
    2 
    3 // MythTV headers
    4 #include "mythcontext.h"
    5 #include "storagegroup.h"
    6 #include "imagescanthread.h"
    7 #include "imageutils.h"
    8 
    9 
    10 /** \fn     ImageScanThread::ImageScanThread()
    11  *  \brief  Constructor
    12  *  \return void
    13  */
    14 ImageScanThread::ImageScanThread() : MThread("ImageScanThread")
    15 {
    16     // initialize all required data structures
    17     m_dbDirList   = new QMap<QString, ImageMetadata *>;
    18     m_dbFileList  = new QMap<QString, ImageMetadata *>;
    19     m_continue = false;
    20 
    21     m_progressCount       = 0;
    22     m_progressTotalCount  = 0;
    23 }
    24 
    25 
    26 
    27 /** \fn     ImageScanThread::~ImageScanThread()
    28  *  \brief  Destructor
    29  *  \return void
    30  */
    31 ImageScanThread::~ImageScanThread()
    32 {
    33     if (m_dbDirList)
    34     {
    35         delete m_dbDirList;
    36         m_dbDirList = NULL;
    37     }
    38 
    39     if (m_dbFileList)
    40     {
    41         delete m_dbFileList;
    42         m_dbFileList = NULL;
    43     }
    44 }
    45 
    46 
    47 
    48 /** \fn     ImageScanThread::run()
    49  *  \brief  Called when the thread is started. Loads all storage groups files
    50  *          and directories and also from the database and syncronizes them.
    51  *  \return void
    52  */
    53 void ImageScanThread::run()
    54 {
    55     RunProlog();
    56 
    57     if (!m_continue)
    58     {
    59         LOG(VB_GENERAL, LOG_DEBUG,
    60             QString("Image scanning thread not allowed to start."));
    61         return;
    62     }
    63 
    64     LOG(VB_GENERAL, LOG_DEBUG, "Syncronisation started");
    65 
    66     m_progressCount       = 0;
    67     m_progressTotalCount  = 0;
    68    
    69     // Load all available directories and files from the database so that
    70     // they can be compared against the ones on the filesystem.
    71     ImageUtils *iu = ImageUtils::getInstance();
    72     iu->LoadDirectoriesFromDB(m_dbDirList);
    73     iu->LoadFilesFromDB(m_dbFileList);
    74 
    75     QStringList paths = iu->GetStorageDirs();
    76 
    77     // Get the total list of directories that will be synced.
    78     // This is only an additional information that the themer can show.
    79     for (int i = 0; i < paths.size(); ++i)
    80     {
    81         QString path = paths.at(i);
    82         QDirIterator it(path, QDirIterator::Subdirectories);
    83 
    84         while(it.hasNext())
    85         {
    86             it.next();
    87             ++m_progressTotalCount;
    88         }
    89     }
    90 
    91     // Now start the actual syncronization
    92     for (int i = 0; i < paths.size(); ++i)
    93     {
    94         QString path = paths.at(i);
    95         QString base = path;
    96         if (!base.endsWith('/'))
    97             base.append('/');
    98         SyncFilesFromDir(path, 0, base);
    99     }
    100 
    101     // Adding or updating directories have been completed.
    102     // The directory list still contains the remaining directories
    103     // that are not in the filesystem anymore. Remove them from the database
    104     QMap<QString, ImageMetadata *>::iterator i;
    105     for (i = m_dbDirList->begin(); i != m_dbDirList->end(); ++i)
    106     {
    107         iu->RemoveDirectoryFromDB(m_dbDirList->value(i.key()));
    108     }
    109 
    110     // Repeat the same for the file list.
    111     for (i = m_dbFileList->begin(); i != m_dbFileList->end(); ++i)
    112     {
    113         iu->RemoveFileFromDB(m_dbFileList->value(i.key()));
    114     }
    115 
    116     m_continue = false;
    117     m_progressCount       = 0;
    118     m_progressTotalCount  = 0;
    119 
    120     LOG(VB_GENERAL, LOG_DEBUG, "Syncronisation complete");
    121 
    122     RunEpilog();
    123 }
    124 
    125 
    126 
    127 /** \fn     ImageScanThread::SyncFilesFromDir(QString &, int)
    128  *  \brief  Loads all available files from the path on the
    129  *          backend and syncs depending if they are a directory or file
    130  *  \param  path The current directory with the files that shall be scanned syncronized
    131  *  \param  parentId The id of the parent directory which is required for possible subdirectories
    132  *  \param  baseDirectory The current root storage group path, this will be stripped before insertion into the database
    133  *  \return void
    134  */
    135 void ImageScanThread::SyncFilesFromDir(QString &path, int parentId,
    136                                        const QString &baseDirectory)
    137 {
    138     if (!m_continue)
    139     {
    140         LOG(VB_FILE, LOG_DEBUG,
    141             QString("Syncing from SG dir %1 interrupted").arg(path));
    142         return;
    143     }
    144 
    145     LOG(VB_FILE, LOG_DEBUG,
    146         QString("Syncing from SG dir %1").arg(path));
    147 
    148     QDir dir(path);
    149     if (!dir.exists())
    150         return;
    151 
    152     // Only get files and dirs, no special and hidden stuff
    153     dir.setFilter(QDir::Dirs | QDir::Files |
    154                   QDir::NoDotAndDotDot | QDir::NoSymLinks);
    155     QFileInfoList list = dir.entryInfoList();
    156     if (list.isEmpty())
    157         return;
    158 
    159     for (QFileInfoList::iterator it = list.begin(); it != list.end(); ++it)
    160     {
    161         if (!m_continue)
    162         {
    163             LOG(VB_FILE, LOG_DEBUG,
    164                 QString("Syncing from SG dir %1 interrupted").arg(path));
    165             return;
    166         }
    167 
    168         QFileInfo fileInfo = *it;
    169         if (fileInfo.isDir())
    170         {
    171             // Get the id. This will be new parent id
    172             // when we traverse down the current directory.
    173             int id = SyncDirectory(fileInfo, parentId, baseDirectory);
    174 
    175             // Get new files within this directory
    176             QString fileName = fileInfo.absoluteFilePath();
    177             SyncFilesFromDir(fileName, id, baseDirectory);
    178         }
    179         else
    180         {
    181             SyncFile(fileInfo, parentId, baseDirectory);
    182         }
    183 
    184         // Increase the current progress count in case a
    185         // progressbar is used to show the sync progress
    186         if (m_progressTotalCount > m_progressCount)
    187             ++m_progressCount;
    188     }
    189 }
    190 
    191 
    192 
    193 /** \fn     ImageScanThread::SyncDirectory(QFileInfo &, int)
    194  *  \brief  Syncronizes a directory with the database.
    195  *          Either inserts or deletes the information in the database.
    196  *  \param  fileInfo The information of the directory
    197  *  \param  parentId The parent directory which will be saved with the file
    198  *  \param  baseDirectory The current root storage group path, this will be stripped before insertion into the database
    199  *  \return void
    200  */
    201 int ImageScanThread::SyncDirectory(QFileInfo &fileInfo, int parentId, const QString &baseDirectory)
    202 {
    203 
    204     LOG(VB_FILE, LOG_DEBUG, QString("Syncing directory %1")
    205         .arg(fileInfo.absoluteFilePath()));
    206 
    207     ImageMetadata *im = new ImageMetadata();
    208 
    209     if (!m_dbDirList->contains(fileInfo.absoluteFilePath()))
    210     {
    211         // Load all required information of the directory
    212         ImageUtils *iu = ImageUtils::getInstance();
    213         iu->LoadDirectoryData(fileInfo, im, parentId, baseDirectory);
    214 
    215         // The directory is not in the database list
    216         // add it to the database and get the new id. This
    217         // will be the new parent id for the subdirectories
    218         im->m_id = iu->InsertDirectoryIntoDB(im);
    219     }
    220     else
    221     {
    222         // The directory exists in the db list
    223         // Get the id which will be the new
    224         // parent id for the subdirectories
    225         im->m_id = m_dbDirList->value(fileInfo.absoluteFilePath())->m_id;
    226 
    227         // Remove the entry from the dbList
    228         // so we don't need to search again
    229         m_dbDirList->remove(fileInfo.absoluteFilePath());
    230     }
    231 
    232     int id = im->m_id;
    233     delete im;
    234 
    235     return id;
    236 }
    237 
    238 
    239 
    240 /** \fn     ImageScanThread::SyncFile(QFileInfo &, int)
    241  *  \brief  Syncronizes a file with the database. Either inserts,
    242  *          updates or deletes the information in the database.
    243  *  \param  fileInfo The information of the file
    244  *  \param  parentId The parent directory which will be saved with the file
    245  *  \return void
    246  */
    247 void ImageScanThread::SyncFile(QFileInfo &fileInfo, int parentId,
    248                                const QString &baseDirectory)
    249 {
    250     LOG(VB_FILE, LOG_DEBUG, QString("Syncing file %1")
    251         .arg(fileInfo.absoluteFilePath()));
    252 
    253     if (!m_dbFileList->contains(fileInfo.absoluteFilePath()))
    254     {
    255         ImageMetadata *im = new ImageMetadata();
    256 
    257         // Load all required information of the file
    258         ImageUtils *iu = ImageUtils::getInstance();
    259         iu->LoadFileData(fileInfo, im, baseDirectory);
    260 
    261         // Only load the file if contains a valid file extension
    262         LOG(VB_FILE, LOG_DEBUG, QString("Type of file %1 is %2, extension %3").arg(im->m_fileName).arg(im->m_type).arg(im->m_extension));
    263         if (im->m_type != kUnknown)
    264         {
    265             // Load any required exif information if the file is an image
    266             if (im->m_type == kImageFile)
    267             {
    268                 bool ok;
    269 
    270                 int exifOrientation = iu->GetExifOrientation(fileInfo.absoluteFilePath(), &ok);
    271                 if (ok)
    272                     im->SetOrientation(exifOrientation, true);
    273 
    274                 int exifDate = iu->GetExifDate(fileInfo.absoluteFilePath(), &ok);
    275                 if (ok)
    276                     im->m_date = exifDate;
    277             }
    278 
    279             // Load the parent id. This is the id of the file's path
    280             im->m_parentId = parentId;
    281 
    282             // The file is not in the database list
    283             // add it to the database.
    284             im->m_id = iu->InsertFileIntoDB(im);
    285         }
    286         delete im;
    287     }
    288     else
    289     {
    290         // Remove the entry from the dbList
    291         // so we don't need to search again
    292         m_dbFileList->remove(fileInfo.absoluteFilePath());
    293     }
    294 }
  • deleted file mythtv/libs/libmythmetadata/imagescanthread.h

    diff --git a/mythtv/libs/libmythmetadata/imagescanthread.h b/mythtv/libs/libmythmetadata/imagescanthread.h
    deleted file mode 100644
    index d18eb58..0000000
    + -  
    1 #ifndef IMAGESCANTHREAD_H
    2 #define IMAGESCANTHREAD_H
    3 
    4 // Qt headers
    5 #include <QApplication>
    6 #include <QFileInfo>
    7 #include <QMap>
    8 
    9 // MythTV headers
    10 #include "mthread.h"
    11 #include "imagemetadata.h"
    12 
    13 class ImageScanThread : public MThread
    14 {
    15 public:
    16     ImageScanThread();
    17     ~ImageScanThread();
    18 
    19     bool m_continue;
    20     int  m_progressCount;
    21     int  m_progressTotalCount;
    22 
    23 protected:
    24     void run();
    25 
    26 private slots:
    27 
    28 private:
    29     void SyncFilesFromDir(QString &path, int parentId, const QString &baseDirectory);
    30     int  SyncDirectory(QFileInfo &fileInfo, int parentId, const QString &baseDirectory);
    31     void SyncFile(QFileInfo &fileInfo, int parentId, const QString &baseDirectory);
    32 
    33     QMap<QString, ImageMetadata *> *m_dbDirList;
    34     QMap<QString, ImageMetadata *> *m_dbFileList;
    35 };
    36 
    37 #endif // IMAGESCANTHREAD_H
  • deleted file mythtv/libs/libmythmetadata/imagethumbgenthread.cpp

    diff --git a/mythtv/libs/libmythmetadata/imagethumbgenthread.cpp b/mythtv/libs/libmythmetadata/imagethumbgenthread.cpp
    deleted file mode 100644
    index 21d8775..0000000
    + -  
    1 // Qt headers
    2 #include <QPainter>
    3 #include <QFile>
    4 
    5 // MythTV headers
    6 #include "mythcontext.h"
    7 #include "mythdirs.h"
    8 #include "mythuihelper.h"
    9 #include "mythsystemlegacy.h"
    10 #include "exitcodes.h"
    11 
    12 #include "imagemetadata.h"
    13 #include "imageutils.h"
    14 #include "imagethumbgenthread.h"
    15 
    16 /** \fn     ImageThumbGenThread::ImageThumbGenThread()
    17  *  \brief  Constructor
    18  *  \return void
    19  */
    20 ImageThumbGenThread::ImageThumbGenThread()
    21         :   m_progressCount(0), m_progressTotalCount(0),
    22             m_width(400), m_height(300),
    23             m_pause(false), m_fileListSize(0)
    24 {
    25     QString sgName = IMAGE_STORAGE_GROUP;
    26     m_storageGroup = StorageGroup(sgName, gCoreContext->GetHostName());
    27 
    28     if (!gCoreContext->IsMasterBackend())
    29         LOG(VB_GENERAL, LOG_ERR, "ImageThumbGenThread MUST be run on the master backend");
    30 }
    31 
    32 
    33 
    34 /** \fn     ImageThumbGenThread::~ImageThumbGenThread()
    35  *  \brief  Destructor
    36  *  \return void
    37  */
    38 ImageThumbGenThread::~ImageThumbGenThread()
    39 {
    40     cancel();
    41     wait();
    42 }
    43 
    44 
    45 
    46 /** \fn     ImageThumbGenThread::run()
    47  *  \brief  Called when the thread starts. Tries to generate
    48  *          thumbnails from the file list until its empty or aborted.
    49  *  \return void
    50  */
    51 void ImageThumbGenThread::run()
    52 {
    53     volatile bool exit = false;
    54 
    55     m_mutex.lock();
    56     m_fileListSize = m_fileList.size();
    57     m_mutex.unlock();
    58 
    59     while (!exit)
    60     {
    61         ImageMetadata *im = NULL;
    62 
    63         m_mutex.lock();
    64         if (!m_fileList.isEmpty())
    65             im = m_fileList.takeFirst();
    66 
    67         // Update the progressbar even if the thumbnail will not be created
    68         emit UpdateThumbnailProgress(m_fileList.size(), m_fileListSize);
    69         m_mutex.unlock();
    70 
    71         if (im)
    72         {
    73             if (im->m_type == kImageFile)
    74             {
    75                 CreateImageThumbnail(im);
    76             }
    77             else if (im->m_type == kVideoFile)
    78             {
    79                 CreateVideoThumbnail(im);
    80             }
    81         }
    82 
    83         delete im;
    84 
    85         m_mutex.lock();
    86         exit = m_fileList.isEmpty();
    87         m_mutex.unlock();
    88 
    89         // Allows the thread to be paused when Pause() was called
    90         m_mutex.lock();
    91         if (m_pause)
    92             m_condition.wait(&m_mutex);
    93         m_mutex.unlock();
    94     }
    95 }
    96 
    97 
    98 
    99 /** \fn     ImageThumbGenThread::CreateImageThumbnail(ImageMetadata *, int)
    100  *  \brief  Creates a thumbnail with the correct size and rotation
    101  *  \param  im The thumbnail details
    102  *  \param  dataid The id of the thumbnail
    103  *  \return void
    104  */
    105 void ImageThumbGenThread::CreateImageThumbnail(ImageMetadata *im)
    106 {
    107     if (QFile(im->m_thumbFileNameList->at(0)).exists())
    108         return;
    109 
    110     QDir dir;
    111     if (!dir.exists(im->m_thumbPath))
    112         dir.mkpath(im->m_thumbPath);
    113 
    114     QString imageFileName = m_storageGroup.FindFile(im->m_fileName);
    115 
    116     QImage image;
    117     if (!image.load(imageFileName))
    118     {
    119         LOG(VB_FILE, LOG_ERR, QString("Failed to create pic thumbnail for %1").arg(imageFileName));
    120         return;
    121     }
    122 
    123     Resize(image);
    124 
    125     QMatrix matrix;
    126     switch (im->GetOrientation())
    127     {
    128     case 1: // If the image is in its original state
    129         break;
    130 
    131     case 2: // The image is horizontally flipped
    132         image = image.mirrored(true, false);
    133         break;
    134 
    135     case 3: // The image is rotated 180°
    136         matrix.rotate(180);
    137         image = image.transformed(matrix, Qt::SmoothTransformation);
    138         break;
    139 
    140     case 4: // The image is vertically flipped
    141         image = image.mirrored(false, true);
    142         break;
    143 
    144     case 5: // The image is transposed (rotated 90° CW flipped horizontally)
    145         matrix.rotate(90);
    146         image = image.transformed(matrix, Qt::SmoothTransformation);
    147         image = image.mirrored(true, false);
    148         break;
    149 
    150     case 6: // The image is rotated 90° CCW
    151         matrix.rotate(270);
    152         image = image.transformed(matrix, Qt::SmoothTransformation);
    153         break;
    154 
    155     case 7: // The image is transversed  (rotated 90° CW and flipped vertically)
    156         matrix.rotate(90);
    157         image = image.transformed(matrix, Qt::SmoothTransformation);
    158         image = image.mirrored(false, true);
    159         break;
    160 
    161     case 8: // The image is rotated 90° CW
    162         matrix.rotate(90);
    163         image = image.transformed(matrix, Qt::SmoothTransformation);
    164         break;
    165 
    166     default:
    167         break;
    168     }
    169 
    170     // save the image in the thumbnail directory
    171     if (image.save(im->m_thumbFileNameList->at(0)))
    172     {
    173         LOG(VB_FILE, LOG_DEBUG, QString("Created pic thumbnail for %1").arg(imageFileName));
    174         QString msg = "IMAGE_THUMB_CREATED %1";
    175         gCoreContext->SendMessage(msg.arg(im->m_id));
    176     }
    177 }
    178 
    179 
    180 
    181 /** \fn     ImageThumbGenThread::CreateVideoThumbnail(ImageMetadata *)
    182  *  \brief  Creates a video preview image with the correct size
    183  *  \param  im The thumbnail details
    184  *  \return void
    185  */
    186 void ImageThumbGenThread::CreateVideoThumbnail(ImageMetadata *im)
    187 {
    188     if (QFile(im->m_thumbFileNameList->at(0)).exists())
    189         return;
    190 
    191     QDir dir;
    192     if (!dir.exists(im->m_thumbPath))
    193         dir.mkpath(im->m_thumbPath);
    194 
    195     QString videoFileName = m_storageGroup.FindFile(im->m_fileName);
    196 
    197     QString cmd = "mythpreviewgen";
    198     QStringList args;
    199     args << logPropagateArgs.split(" ", QString::SkipEmptyParts);
    200     args << "--infile"  << '"' + videoFileName + '"';
    201     args << "--outfile" << '"' + im->m_thumbFileNameList->at(0) + '"';
    202 
    203     MythSystemLegacy ms(cmd, args, kMSRunShell);
    204     ms.SetDirectory(im->m_thumbPath);
    205     ms.Run();
    206 
    207     // If the process exited successful
    208     // then try to load the thumbnail
    209     if (ms.Wait() == GENERIC_EXIT_OK)
    210     {
    211         QImage image;
    212         if (!image.load(im->m_thumbFileNameList->at(0)))
    213             return;
    214 
    215         Resize(image);
    216 
    217         // save the default image in the thumbnail directory
    218         if (image.save(im->m_thumbFileNameList->at(0)))
    219         {
    220             emit ThumbnailCreated(im, 0);
    221             QString msg = "IMAGE_THUMB_CREATED %1";
    222             gCoreContext->SendMessage(msg.arg(im->m_id));
    223         }
    224     }
    225 }
    226 
    227 
    228 
    229 /** \fn     ImageThumbGenThread::Resize(QImage)
    230  *  \brief  Resizes the thumbnail to prevent black areas
    231  *          around the image when its shown in a widget.
    232  *  \param  The image that shall be resized
    233  *  \return void
    234  */
    235 void ImageThumbGenThread::Resize(QImage &image)
    236 {
    237     QSize size = QSize(m_width, m_height);
    238 
    239     image = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
    240 }
    241 
    242 
    243 
    244 /** \fn     ImageThumbGenThread::AddToThumbnailList(ImageMetadata *)
    245  *  \brief  Adds a file to the thumbnail list
    246  *  \param  im The file information
    247  *  \param  recreate Force thumbnail regeneration even if it already exists
    248  *  \return void
    249  */
    250 void ImageThumbGenThread::AddToThumbnailList(ImageMetadata *im,
    251                                              bool recreate)
    252 {
    253     if (!im)
    254         return;
    255 
    256     if (recreate)
    257         // remove any existing thumbnail to force its regeneration
    258         QFile::remove(im->m_thumbFileNameList->at(0));
    259 
    260     m_mutex.lock();
    261     m_fileList.append(im);
    262     m_fileListSize = m_fileList.size();
    263     m_mutex.unlock();
    264 }
    265 
    266 
    267 
    268 /** \fn     ImageThumbGenThread::cancel()
    269  *  \brief  Clears the thumbnail list so that the thread can exit.
    270  *  \return void
    271  */
    272 void ImageThumbGenThread::cancel()
    273 {
    274     m_mutex.lock();
    275     while (!m_fileList.isEmpty())
    276         delete m_fileList.takeFirst();
    277     m_fileListSize = 0;
    278     m_mutex.unlock();
    279 
    280     emit UpdateThumbnailProgress(0, 0);
    281 }
    282 
    283 
    284 
    285 /** \fn     ImageThumbGenThread::Pause()
    286  *  \brief  Pauses the thumbnail generation
    287  *  \return void
    288  */
    289 void ImageThumbGenThread::Pause()
    290 {
    291     m_pause = true;
    292 }
    293 
    294 
    295 
    296 /** \fn     ImageThumbGenThread::Resume()
    297  *  \brief  Resumes the thumbnail generation
    298  *  \return void
    299  */
    300 void ImageThumbGenThread::Resume()
    301 {
    302     m_condition.wakeAll();
    303     m_pause = false;
    304 }
    305 
    306 
    307 
    308 //////////////////////////////////////////////////////////////////////////
    309 
    310 
    311 ImageThumbGen* ImageThumbGen::m_instance = NULL;
    312 
    313 ImageThumbGen::ImageThumbGen()
    314 {
    315     m_imageThumbGenThread = new ImageThumbGenThread();
    316 }
    317 
    318 
    319 
    320 ImageThumbGen::~ImageThumbGen()
    321 {
    322     delete m_imageThumbGenThread;
    323     m_imageThumbGenThread = NULL;
    324 }
    325 
    326 
    327 
    328 ImageThumbGen* ImageThumbGen::getInstance()
    329 {
    330     if (!m_instance)
    331         m_instance = new ImageThumbGen();
    332 
    333     return m_instance;
    334 }
    335 
    336 
    337 
    338 void ImageThumbGen::StartThumbGen()
    339 {
    340     if (m_imageThumbGenThread && !m_imageThumbGenThread->isRunning())
    341         m_imageThumbGenThread->start();
    342 }
    343 
    344 
    345 
    346 void ImageThumbGen::StopThumbGen()
    347 {
    348     if (m_imageThumbGenThread && m_imageThumbGenThread->isRunning())
    349         m_imageThumbGenThread->cancel();
    350 }
    351 
    352 
    353 
    354 bool ImageThumbGen::ThumbGenIsRunning()
    355 {
    356     if (m_imageThumbGenThread)
    357         return m_imageThumbGenThread->isRunning();
    358 
    359     return false;
    360 }
    361 
    362 
    363 
    364 int ImageThumbGen::GetCurrent()
    365 {
    366     if (m_imageThumbGenThread)
    367         return m_imageThumbGenThread->m_progressCount;
    368 
    369     return 0;
    370 }
    371 
    372 
    373 
    374 int ImageThumbGen::GetTotal()
    375 {
    376     if (m_imageThumbGenThread)
    377         return m_imageThumbGenThread->m_progressTotalCount;
    378 
    379     return 0;
    380 }
    381 
    382 
    383 
    384 bool ImageThumbGen::AddToThumbnailList(ImageMetadata *im,
    385                                        bool recreate)
    386 {
    387     if (!m_imageThumbGenThread)
    388         return false;
    389 
    390     m_imageThumbGenThread->AddToThumbnailList(im, recreate);
    391 
    392     return true;
    393 }
  • deleted file mythtv/libs/libmythmetadata/imagethumbgenthread.h

    diff --git a/mythtv/libs/libmythmetadata/imagethumbgenthread.h b/mythtv/libs/libmythmetadata/imagethumbgenthread.h
    deleted file mode 100644
    index 9a50606..0000000
    + -  
    1 #ifndef GALLERYTHUMBGENTHREAD_H
    2 #define GALLERYTHUMBGENTHREAD_H
    3 
    4 // Qt headers
    5 #include <QThread>
    6 #include <QMutex>
    7 #include <QWaitCondition>
    8 
    9 // MythTV headers
    10 #include "mythuibuttontree.h"
    11 #include "imagemetadata.h"
    12 #include "storagegroup.h"
    13 #include "mythmetaexp.h"
    14 
    15 class META_PUBLIC ImageThumbGenThread : public QThread
    16 {
    17     Q_OBJECT
    18 
    19   public:
    20     ImageThumbGenThread();
    21     ~ImageThumbGenThread();
    22 
    23     void cancel();
    24     void Pause();
    25     void Resume();
    26     void AddToThumbnailList(ImageMetadata *, bool);
    27     void SetThumbnailSize(int, int);
    28 
    29     int m_progressCount;
    30     int m_progressTotalCount;
    31 
    32   signals:
    33     void ThumbnailCreated(ImageMetadata *, int);
    34     void UpdateThumbnailProgress(int, int);
    35 
    36   protected:
    37     void run();
    38 
    39   private:
    40     void CreateImageThumbnail(ImageMetadata *);
    41     void CreateVideoThumbnail(ImageMetadata *);
    42 
    43     void Resize(QImage &);
    44     void Rotate(QImage &);
    45     void Combine(QImage &, QImage &, QPoint);
    46     void DrawBorder(QImage &);
    47 
    48     QList<ImageMetadata *>    m_fileList;
    49     QMutex              m_mutex;
    50 
    51     int m_width;
    52     int m_height;
    53     bool m_pause;
    54     int m_fileListSize;
    55 
    56     QWaitCondition      m_condition;
    57     StorageGroup        m_storageGroup;
    58 };
    59 
    60 class META_PUBLIC ImageThumbGen
    61 {
    62   public:
    63     static ImageThumbGen*    getInstance();
    64 
    65     void StartThumbGen();
    66     void StopThumbGen();
    67     bool ThumbGenIsRunning();
    68 
    69     bool AddToThumbnailList(ImageMetadata *, bool);
    70 
    71     bool SetThumbnailSize(int width, int height);
    72 
    73     int  GetCurrent();
    74     int  GetTotal();
    75 
    76   private:
    77     ImageThumbGen();
    78     ~ImageThumbGen();
    79     static ImageThumbGen    *m_instance;
    80 
    81     ImageThumbGenThread     *m_imageThumbGenThread;
    82 };
    83 
    84 #endif // GALLERYTHUMBGENTHREAD_H
  • new file mythtv/libs/libmythmetadata/imagethumbs.cpp

    diff --git a/mythtv/libs/libmythmetadata/imagethumbs.cpp b/mythtv/libs/libmythmetadata/imagethumbs.cpp
    new file mode 100644
    index 0000000..14b1327
    - +  
     1#include "imagethumbs.h"
     2
     3#include <exitcodes.h> // for previewgen
     4
     5#include <QFile>
     6#include <QDir>
     7#include <QtAlgorithms>
     8#include <QImage>
     9#include <QThread>
     10#include <QMutexLocker>
     11#include <QMatrix>
     12
     13#include <mythdirs.h>
     14#include <mythsystemlegacy.h>
     15
     16
     17/*!
     18 \brief Contstruct request for a single image
     19 \param action Request action
     20 \param im Image object that will be deleted.
     21 \param priority Request priority
     22 \param notify If true a 'thumbnail exists' event will be broadcast when done.
     23*/
     24ThumbTask::ThumbTask(QString action, ImageItem *im,
     25                     ImageThumbPriority priority, bool notify)
     26    : m_action(action),
     27      m_priority(priority),
     28      m_notify(notify)
     29{
     30    append(im);
     31}
     32
     33
     34/*!
     35 \brief Contstruct request for a list of images/dirs
     36 \param action Request action
     37 \param list Image objects that will be deleted.
     38 \param priority Request priority
     39 \param notify If true a 'thumbnail exists' event will be broadcast when done.
     40*/
     41ThumbTask::ThumbTask(QString action, ImageList &list,
     42                     ImageThumbPriority priority, bool notify)
     43    : ImageList(list),
     44      m_action(action),
     45      m_priority(priority),
     46      m_notify(notify)
     47{
     48    // Assume ownership of list contents
     49    list.clear();
     50}
     51
     52
     53/*!
     54 \brief  Construct worker thread
     55*/
     56ThumbThread::ThumbThread(QString name)
     57    : MThread(name), m_sg(ImageSg::getInstance())
     58{
     59    m_tempDir = QString("%1/%2").arg(GetConfDir(), TEMP_DIR);
     60    m_thumbDir = m_tempDir.absoluteFilePath(THUMBNAIL_DIR);
     61    m_tempDir.mkdir(THUMBNAIL_DIR);
     62
     63    // Use priorities: 0 = image requests, 1 = video requests, 2 = urgent, 3 = background
     64    for (int i = 0; i <= kBackgroundPriority; ++i)
     65        m_thumbQueue.insert(static_cast<ImageThumbPriority>(i), new ThumbQueue());
     66
     67    if (!gCoreContext->IsBackend())
     68        LOG(VB_GENERAL, LOG_ERR, "Thumbnail Generators MUST be run on a backend");
     69}
     70
     71
     72/*!
     73 \brief  Destructor
     74*/
     75ThumbThread::~ThumbThread()
     76{
     77    cancel();
     78    wait();
     79    qDeleteAll(m_thumbQueue);
     80}
     81
     82
     83/*!
     84 \brief  Handles thumbnail requests by priority
     85 \details Repeatedly processes next request from highest priority queue until all
     86 queues are empty, then quits. For Create requests an event is broadcast once the
     87 thumbnail exists. Dirs are only deleted if empty
     88 */
     89void ThumbThread::run()
     90{
     91    RunProlog();
     92
     93    setPriority(QThread::LowestPriority);
     94
     95    while (true)
     96    {
     97        // process next highest-priority task
     98        ThumbTask *task = NULL;
     99        {
     100            QMutexLocker locker(&m_mutex);
     101            foreach(ThumbQueue *q, m_thumbQueue)
     102                if (!q->isEmpty())
     103                {
     104                    task = q->takeFirst();
     105                    break;
     106                }
     107        }
     108        // quit when all queues exhausted
     109        if (!task)
     110            break;
     111
     112        // Shouldn't receive empty requests
     113        if (task->isEmpty())
     114            continue;
     115
     116        if (task->m_action == "CREATE")
     117        {
     118            ImageItem *im = task->at(0);
     119
     120            LOG(VB_FILE, LOG_DEBUG, objectName()
     121                + QString(": Creating %1 (Id %2, priority %3)")
     122                .arg(im->m_fileName).arg(im->m_id).arg(task->m_priority));
     123
     124            // Shouldn't receive any dirs or empty thumb lists
     125            if (im->m_thumbPath.isEmpty())
     126                continue;
     127
     128            if (m_tempDir.exists(im->m_thumbPath))
     129
     130                LOG(VB_FILE, LOG_DEBUG, objectName()
     131                    + QString(": Thumbnail %1 already exists")
     132                    .arg(im->m_thumbPath));
     133
     134            else if (im->m_type == kImageFile)
     135
     136                CreateImageThumbnail(im);
     137
     138            else if (im->m_type == kVideoFile)
     139
     140                CreateVideoThumbnail(im);
     141
     142            else
     143                LOG(VB_FILE, LOG_ERR, objectName()
     144                    + QString(": Can't create thumbnail for type %1 : image %2")
     145                    .arg(im->m_type).arg(im->m_fileName));
     146
     147            // notify clients when done
     148            if (task->m_notify)
     149            {
     150                QString id = QString::number(im->m_id);
     151
     152                // Return requested thumbnails - FE uses it as a message signature
     153                MythEvent me = MythEvent("THUMB_AVAILABLE", id);
     154                gCoreContext->SendEvent(me);
     155            }
     156        }
     157        else if (task->m_action == "DELETE")
     158        {
     159            foreach(const ImageItem *im, *task)
     160            {
     161                if (m_tempDir.remove(im->m_thumbPath))
     162
     163                    LOG(VB_FILE, LOG_DEBUG, objectName()
     164                        + QString(": Deleted thumbnail %1")
     165                        .arg(im->m_fileName));
     166                else
     167                    LOG(VB_FILE, LOG_WARNING, objectName()
     168                        + QString(": Couldn't delete thumbnail %1")
     169                        .arg(im->m_thumbPath));
     170            }
     171        }
     172        else if (task->m_action == "DELETE_DIR")
     173        {
     174            ImageList::const_iterator it = (*task).constEnd();
     175            while (it != (*task).constBegin())
     176            {
     177                ImageItem *im = *(--it);
     178
     179                if (m_tempDir.rmdir(im->m_thumbPath))
     180
     181                    LOG(VB_FILE, LOG_DEBUG, objectName()
     182                        + QString(": Deleted thumbdir %1")
     183                        .arg(im->m_fileName));
     184                else
     185                    LOG(VB_FILE, LOG_WARNING, objectName()
     186                        + QString(": Couldn't delete thumbdir %1")
     187                        .arg(im->m_thumbPath));
     188            }
     189        }
     190        else
     191            LOG(VB_FILE, LOG_ERR, objectName() + QString(": Unknown task %1")
     192                .arg(task->m_action));
     193
     194        qDeleteAll(*task);
     195        delete task;
     196    }
     197
     198    RunEpilog();
     199}
     200
     201
     202/*!
     203 \brief Rotates/reflects an image iaw its orientation
     204 \note Duplicates MythImage::Orientation
     205 \param im Image details
     206 \param image Image to be transformed
     207*/
     208void ThumbThread::Orientate(ImageItem *im, QImage &image)
     209{
     210    QMatrix matrix;
     211    switch (im->m_orientation)
     212    {
     213    case 1: // If the image is in its original state
     214        break;
     215
     216    case 2: // The image is horizontally flipped
     217        image = image.mirrored(true, false);
     218        break;
     219
     220    case 3: // The image is rotated 180°
     221        matrix.rotate(180);
     222        image = image.transformed(matrix, Qt::SmoothTransformation);
     223        break;
     224
     225    case 4: // The image is vertically flipped
     226        image = image.mirrored(false, true);
     227        break;
     228
     229    case 5: // The image is transposed (flipped horizontally, then rotated 90° CCW)
     230        matrix.rotate(90);
     231        image = image.transformed(matrix, Qt::SmoothTransformation);
     232        image = image.mirrored(true, false);
     233        break;
     234
     235    case 6: // The image is rotated 90° CCW
     236        matrix.rotate(90);
     237        image = image.transformed(matrix, Qt::SmoothTransformation);
     238        break;
     239
     240    case 7: // The image is transversed (flipped horizontally, then rotated 90° CW)
     241        matrix.rotate(270);
     242        image = image.transformed(matrix, Qt::SmoothTransformation);
     243        image = image.mirrored(true, false);
     244        break;
     245
     246    case 8: // The image is rotated 90° CW
     247        matrix.rotate(270);
     248        image = image.transformed(matrix, Qt::SmoothTransformation);
     249        break;
     250
     251    default:
     252        break;
     253    }
     254}
     255
     256/*!
     257 \brief  Creates a picture thumbnail with the correct size and rotation
     258 \param  im The image
     259*/
     260void ThumbThread::CreateImageThumbnail(ImageItem *im)
     261{
     262    QString imagePath = m_sg->GetFilePath(im);
     263
     264    if (!im->m_path.isEmpty())
     265        m_thumbDir.mkpath(im->m_path);
     266
     267    // Absolute path of the BE thumbnail
     268    QString thumbPath = m_tempDir.absoluteFilePath(im->m_thumbPath);
     269
     270    QImage image;
     271    if (!image.load(imagePath))
     272    {
     273        LOG(VB_FILE, LOG_ERR, QString("%1: Failed to open image %2")
     274            .arg(objectName(), imagePath));
     275        return;
     276    }
     277
     278    // Resize & orientate now to optimise load/display time by FE's
     279    image = image.scaled(QSize(240,180), Qt::KeepAspectRatio, Qt::SmoothTransformation);
     280    Orientate(im, image);
     281
     282    // create the thumbnail
     283    if (image.save(thumbPath))
     284
     285        LOG(VB_FILE, LOG_INFO, QString("%1: Created thumbnail for %2")
     286            .arg(objectName(), imagePath));
     287    else
     288        LOG(VB_FILE, LOG_ERR, QString("%1: Failed to create thumbnail for %2")
     289            .arg(objectName(), imagePath));
     290}
     291
     292
     293/*!
     294 \brief  Creates a video preview image with the correct size using mythpreviewgen
     295 \param  im The image
     296*/
     297void ThumbThread::CreateVideoThumbnail(ImageItem *im)
     298{
     299    QString videoPath = m_sg->GetFilePath(im);
     300
     301    if (!im->m_path.isEmpty())
     302        m_thumbDir.mkpath(im->m_path);
     303
     304    // Absolute path of the BE thumbnail
     305    QString thumbPath = m_tempDir.absoluteFilePath(im->m_thumbPath);
     306
     307    QString cmd = "mythpreviewgen";
     308    QStringList args;
     309    args << logPropagateArgs.split(" ", QString::SkipEmptyParts);
     310    args << "--size 320x240"; // Video thumbnails are shown in slideshow
     311    args << "--infile"  << QString("\"%1\"").arg(videoPath);
     312    args << "--outfile" << QString("\"%1\"").arg(thumbPath);
     313
     314    MythSystemLegacy ms(cmd, args, kMSRunShell);
     315    ms.SetDirectory(m_thumbDir.absolutePath());
     316    ms.Run();
     317
     318    // If the process exited successful
     319    // then try to load the thumbnail
     320    if (ms.Wait() != GENERIC_EXIT_OK)
     321    {
     322        LOG(VB_FILE, LOG_ERR, QString("%1: Preview Generator failed for %2")
     323            .arg(objectName(), videoPath));
     324        return;
     325    }
     326
     327    QImage image;
     328    if (image.load(thumbPath))
     329    {
     330        Orientate(im, image);
     331
     332        image.save(thumbPath);
     333
     334        LOG(VB_FILE, LOG_INFO, QString("%1: Created thumbnail for %2")
     335            .arg(objectName(), videoPath));
     336    }
     337    else
     338        LOG(VB_FILE, LOG_ERR, QString("%1: Failed to create thumbnail for %2")
     339            .arg(objectName(), videoPath));
     340}
     341
     342
     343/*!
     344 \brief Queues a Create request
     345 \param task The request
     346 */
     347void ThumbThread::QueueThumbnails(ThumbTask *task)
     348{
     349    // null tasks will terminate the thread prematurely
     350    if (task)
     351    {
     352        QMutexLocker locker(&m_mutex);
     353        m_thumbQueue.value(task->m_priority)->append(task);
     354
     355        // restart if not already running
     356        if (!this->isRunning())
     357            this->start();
     358    }
     359}
     360
     361
     362/*!
     363 \brief Return size of a specific queue
     364 \param priority The queue of interest
     365 \return int Number of requests pending
     366*/
     367int ThumbThread::GetQueueSize(ImageThumbPriority priority)
     368{
     369    QMap<int,int> result;
     370
     371    QMutexLocker locker(&m_mutex);
     372    return m_thumbQueue.value(priority)->size();
     373}
     374
     375
     376/*!
     377 \brief Clears thumbnail cache
     378*/
     379void ThumbThread::ClearThumbnails()
     380{
     381    LOG(VB_FILE, LOG_INFO, objectName() + ": Removing all thumbnails");
     382
     383    // Clear all queues & wait for generator thread to terminate
     384    cancel();
     385    wait();
     386
     387    // Remove all thumbnails
     388    RemoveDirContents(m_thumbDir.absolutePath());
     389}
     390
     391
     392/*!
     393 \brief Clears all files and sub-dirs within a directory
     394 \param dirName Dir to clear
     395 \return bool True on success
     396*/
     397bool ThumbThread::RemoveDirContents(QString dirName)
     398{
     399    // Delete all files
     400    QDir dir = QDir(dirName);
     401    bool result = true;
     402
     403    foreach(const QFileInfo &info, dir.entryInfoList(QDir::AllEntries
     404                                                     | QDir::NoDotAndDotDot))
     405    {
     406        if (info.isDir())
     407        {
     408            RemoveDirContents(info.absoluteFilePath());
     409            result = dir.rmdir(info.absoluteFilePath());
     410        }
     411        else
     412            result = QFile::remove(info.absoluteFilePath());
     413
     414        if (!result)
     415            LOG(VB_FILE, LOG_ERR, QString("%1: Can't delete %2")
     416                .arg(objectName(), info.absoluteFilePath()));
     417    }
     418    return result;
     419}
     420
     421
     422/*!
     423 \brief Clears all queues so that the thread can terminate.
     424*/
     425void ThumbThread::cancel()
     426{
     427    // Clear all queues
     428    QMutexLocker locker(&m_mutex);
     429    foreach(ThumbQueue *q, m_thumbQueue)
     430    {
     431        qDeleteAll(*q);
     432        q->clear();
     433    }
     434}
     435
     436
     437//////////////////////////////////////////////////////////////////////////
     438
     439
     440//! Thumbnail generator singleton
     441ImageThumb* ImageThumb::m_instance = NULL;
     442
     443
     444/*!
     445 \brief Constructor
     446*/
     447ImageThumb::ImageThumb()
     448{
     449    m_imageThumbThread = new ThumbThread("ImageThumbGen");
     450    m_videoThumbThread = new ThumbThread("VideoThumbGen");
     451}
     452
     453
     454/*!
     455 \brief Destructor
     456*/
     457ImageThumb::~ImageThumb()
     458{
     459    delete m_imageThumbThread;
     460    m_imageThumbThread = NULL;
     461    delete m_videoThumbThread;
     462    m_videoThumbThread = NULL;
     463}
     464
     465
     466/*!
     467 \brief Get generator
     468 \return ImageThumb Generator singleton
     469*/
     470ImageThumb* ImageThumb::getInstance()
     471{
     472    if (!m_instance)
     473        m_instance = new ImageThumb();
     474
     475    return m_instance;
     476}
     477
     478
     479/*!
     480 \brief Return size of specific queue
     481 \param priority Queue of interest
     482 \return int Number of requests pending
     483*/
     484int ImageThumb::GetQueueSize(ImageThumbPriority priority)
     485{
     486    // Ignore video thread
     487    if (m_imageThumbThread)
     488        return m_imageThumbThread->GetQueueSize(priority);
     489    return 0;
     490}
     491
     492
     493/*!
     494 \brief Clears thumbnail cache, blocking until generator thread terminates
     495*/
     496void ImageThumb::ClearAllThumbs()
     497{
     498    // Image task will clear videos as well
     499    if (m_imageThumbThread)
     500        m_imageThumbThread->ClearThumbnails();
     501}
     502
     503
     504/*!
     505 \brief Creates thumbnails on-demand from clients
     506 \details Display requests are the highest priority. Thumbnails required for an image
     507node will be created before those that are part of a directory thumbnail.
     508A THUMBNAIL_CREATED event is broadcast for each image.
     509 \param imList List of images requiring thumbnails
     510*/
     511void ImageThumb::HandleCreateThumbnails(QStringList imList)
     512{
     513    if (imList.size() != 2)
     514        return;
     515
     516    bool isForFolder = imList[1].toInt();
     517
     518    // get specific image details from db
     519    ImageList images;
     520    ImageDbWriter db;
     521    db.ReadDbItemsById(images, imList[0]);
     522
     523    foreach (ImageItem *im, images)
     524    {
     525        ImageThumbPriority priority = isForFolder
     526                ? kFolderRequestPriority : kPicRequestPriority;
     527
     528        // notify clients when done; highest priority
     529        ThumbTask *task = new ThumbTask("CREATE", im, priority, true);
     530
     531        if (im->m_type == kVideoFile)
     532        {
     533            if (m_videoThumbThread)
     534                m_videoThumbThread->QueueThumbnails(task);
     535        }
     536        else if (im->m_type == kImageFile)
     537        {
     538            if (m_imageThumbThread)
     539                m_imageThumbThread->QueueThumbnails(task);
     540        }
     541    }
     542}
     543
     544
     545/*!
     546 \brief Remove thumbnails from cache
     547 \param images List of obselete images
     548 \param dirs List of obselete dirs
     549 \return QStringList Csv list of deleted ids, empty (no modified ids), csv list of
     550 deleted thumbnail and image urls (compatible with FE cache)
     551*/
     552QStringList ImageThumb::DeleteThumbs(ImageList images, ImageList dirs)
     553{
     554    // Determine affected images and redundant images/thumbnails
     555    QStringList mesg = QStringList(""); // Empty item (no modified ids)
     556    QStringList ids;
     557    ImageSg *isg = ImageSg::getInstance();
     558
     559    foreach (const ImageItem *im, images)
     560    {
     561        ids << QString::number(im->m_id);
     562        // Remove thumbnail
     563        mesg << isg->GenerateThumbUrl(im->m_thumbPath);
     564        // Remove cached image
     565        mesg << isg->GenerateUrl(im->m_fileName);
     566    }
     567    // Insert deleted ids at front
     568    mesg.insert(0, ids.join(","));
     569
     570    if (!m_imageThumbThread)
     571        return QStringList();
     572
     573    // FIXME: Video thread could be affected
     574    if (!images.isEmpty())
     575        // Delete BE thumbs with high priority to prevent future client
     576        // requests from usurping the Delete and using the old thumbs.
     577        // Thumb generator now owns the image objects
     578        m_imageThumbThread->QueueThumbnails(new ThumbTask("DELETE",
     579                                                          images,
     580                                                          kPicRequestPriority));
     581    if (!dirs.isEmpty())
     582        // Clean up thumbdirs as low priority
     583        m_imageThumbThread->QueueThumbnails(new ThumbTask("DELETE_DIR", dirs));
     584
     585    return mesg;
     586}
     587
     588
     589/*!
     590 \brief Creates thumbnails for new images/dirs detected by scanner
     591 \param im Image
     592 \param priority Request priority
     593*/
     594void ImageThumb::CreateThumbnail(ImageItem *im, ImageThumbPriority priority)
     595{
     596    ThumbTask *task = new ThumbTask("CREATE", im, priority);
     597
     598    if (im->m_type == kVideoFile)
     599    {
     600        if (m_videoThumbThread)
     601            m_videoThumbThread->QueueThumbnails(task);
     602    }
     603    else if (im->m_type == kImageFile)
     604    {
     605        if (m_imageThumbThread)
     606            m_imageThumbThread->QueueThumbnails(task);
     607    }
     608}
  • new file mythtv/libs/libmythmetadata/imagethumbs.h

    diff --git a/mythtv/libs/libmythmetadata/imagethumbs.h b/mythtv/libs/libmythmetadata/imagethumbs.h
    new file mode 100644
    index 0000000..e28e043
    - +  
     1//! \file
     2//! \brief Creates and manages thumbnails in the cache
     3//! \details Uses two worker threads to process thumbnail requests that are queued.
     4//! One for pictures and a one for videos, which are off-loaded to previewgenerator,
     5//! and time-consuming. Both background threads are low-priority to avoid recording issues.
     6//! Requests are handled by client-assigned priority so that on-demand display requests
     7//! are serviced before background pre-generation requests.
     8//! When images are removed, their thumbnails are also deleted (thumbnail cache is
     9//! synchronised to database). Obselete thumbnails are broadcast to enable clients to
     10//! also manage/synchronise their caches.
     11
     12#ifndef IMAGETHUMBGEN_H
     13#define IMAGETHUMBGEN_H
     14
     15// Qt headers
     16#include <QMutex>
     17#include <QList>
     18#include <QMap>
     19#include <QDir>
     20#include <QImage>
     21
     22// MythTV headers
     23#include <mthread.h>
     24#include <imageutils.h>
     25
     26
     27//! \brief Priority of a thumbnail request. First/lowest are handled before later/higher
     28//! \details Ordered to optimise perceived client performance, ie. pictures will be
     29//! displayed before directories (4 thumbnails), then videos (slow to generate) are filled
     30//! in last.
     31typedef enum priorities {
     32    kPicRequestPriority    = 0, //!< Client request to display an image thumbnail
     33    kFolderRequestPriority = 1, //!< Client request to display a directory thumbnail
     34    kVideoRequestPriority  = 2, //!< Client request to display a video preview
     35    kScannerUrgentPriority = 3, //!< Scanner request needed to complete a scan
     36    kBackgroundPriority    = 4  //!< Scanner background request
     37} ImageThumbPriority;
     38
     39
     40//! A generator request that is queued
     41class META_PUBLIC ThumbTask : public ImageList
     42{
     43public:
     44    ThumbTask(const QString &,
     45              ImageThumbPriority = kBackgroundPriority, bool = false);
     46    ThumbTask(QString, ImageItem*,
     47              ImageThumbPriority = kBackgroundPriority, bool = false);
     48    ThumbTask(const QString &, ImageMap&,
     49              ImageThumbPriority = kBackgroundPriority, bool = false);
     50    ThumbTask(QString, ImageList&,
     51              ImageThumbPriority = kBackgroundPriority, bool = false);
     52
     53    //! Request action: Create, delete etc.
     54    QString m_action;
     55    //! Request reason/priority
     56    ImageThumbPriority m_priority;
     57    //! If true, a "THUMBNAIL_CREATED" event is broadcast
     58    bool m_notify;
     59};
     60
     61
     62//! A generator worker thread
     63class META_PUBLIC ThumbThread : public MThread
     64{
     65  public:
     66    ThumbThread(QString name);
     67    ~ThumbThread();
     68
     69    void QueueThumbnails(ThumbTask *);
     70    void ClearThumbnails();
     71    int GetQueueSize(ImageThumbPriority);
     72
     73  protected:
     74    void run();
     75    void cancel();
     76
     77  private:
     78    void CreateImageThumbnail(ImageItem *);
     79    void CreateVideoThumbnail(ImageItem *);
     80    bool RemoveDirContents(QString);
     81    void Orientate(ImageItem *im, QImage &image);
     82
     83    //! A queue of generator requests
     84    typedef QList<ThumbTask *> ThumbQueue;
     85    //! A priority queue where 0 is highest priority
     86    QMap<ImageThumbPriority, ThumbQueue *> m_thumbQueue;
     87    //! Queue protection
     88    QMutex m_mutex;
     89
     90    //! Storage Group accessor
     91    ImageSg *m_sg;
     92
     93    //! Path of backend thumbnail cache
     94    QDir m_thumbDir;
     95    //! Path of backend temp
     96    QDir m_tempDir;
     97};
     98
     99
     100//! Singleton creating/managing image thumbnails
     101class META_PUBLIC ImageThumb
     102{
     103  public:
     104    static ImageThumb* getInstance();
     105
     106    void        CreateThumbnail(ImageItem *, ImageThumbPriority);
     107    void        HandleCreateThumbnails(QStringList imlist);
     108    int         GetQueueSize(ImageThumbPriority);
     109    void        ClearAllThumbs();
     110    QStringList DeleteThumbs(ImageList, ImageList);
     111
     112  private:
     113    ImageThumb();
     114    ~ImageThumb();
     115
     116    //! Singleton
     117    static ImageThumb *m_instance;
     118
     119    //! Worker thread generating picture thumbnails
     120    ThumbThread       *m_imageThumbThread;
     121    //! Worker thread generating video previews
     122    ThumbThread       *m_videoThumbThread;
     123};
     124
     125#endif // IMAGETHUMBGEN_H
  • mythtv/libs/libmythmetadata/imageutils.cpp

    diff --git a/mythtv/libs/libmythmetadata/imageutils.cpp b/mythtv/libs/libmythmetadata/imageutils.cpp
    index 816bb54..4b44a89 100644
    a b  
    1 // Qt headers
    2 
    3 // MythTV headers
    4 #include "mythcontext.h"
    5 #include "mythdirs.h"
    6 #include "storagegroup.h"
    71#include "imageutils.h"
    82
     3#include <QByteArray>
     4#include <QMutableListIterator>
     5#include <QImageReader>
    96
    10 // The maximum possible value of the utc time
    11 #define MAX_UTCTIME 2147483646;
     7#include <dbaccess.h>
     8#include <mythdirs.h>
    129
    13 ImageUtils* ImageUtils::m_instance = NULL;
    1410
    15 ImageUtils::ImageUtils()
     11/**
     12 *  \brief  Constructor
     13 */
     14ImageItem::ImageItem() :
     15    m_id(0),
     16    m_name(""),
     17    m_path(""),
     18    m_parentId(0),
     19    m_type(0),
     20    m_modTime(0),
     21    m_size(0),
     22    m_extension(""),
     23    m_date(0),
     24    m_orientation(0),
     25    m_comment(""),
     26    m_isHidden(false),
     27    m_userThumbnail(0),
     28    m_fileName(""),
     29    m_thumbPath(""),
     30    m_dirCount(0),
     31    m_fileCount(0)
    1632{
    17     m_imageFileExt = QString("jpg,jpeg,png,tif,tiff,bmp,gif").split(",");
    18     m_videoFileExt = QString("avi,mpg,mp4,mpeg,mov,wmv,3gp").split(",");
    1933}
    2034
    2135
    22 
    23 ImageUtils::~ImageUtils()
     36ImageItem::ImageItem(const ImageItem &im) :
     37    m_id(im.m_id),
     38    m_name(im.m_name),
     39    m_path(im.m_path),
     40    m_parentId(im.m_parentId),
     41    m_type(im.m_type),
     42    m_modTime(im.m_modTime),
     43    m_size(im.m_size),
     44    m_extension(im.m_extension),
     45    m_date(im.m_date),
     46    m_orientation(im.m_orientation),
     47    m_comment(im.m_comment),
     48    m_isHidden(im.m_isHidden),
     49    m_userThumbnail(im.m_userThumbnail),
     50    m_fileName(im.m_fileName),
     51    m_thumbPath(im.m_thumbPath),
     52    m_thumbNails(im.m_thumbNails),
     53    m_thumbIds(im.m_thumbIds),
     54    m_dirCount(im.m_dirCount),
     55    m_fileCount(im.m_fileCount)
    2456{
    25 
    2657}
    2758
    2859
    29 
    30 ImageUtils* ImageUtils::getInstance()
     60/*!
     61 * \brief Get path for an image thumbnail
     62 * \param im The image
     63 * \return QString Thumbnail path
     64 */
     65QString ImageUtils::ThumbPathOf(ImageItem *im)
    3166{
    32     if (!m_instance)
    33         m_instance = new ImageUtils();
     67    // Thumbnails of videos are a JPEG snapshot with jpg suffix appended
     68    QString ext = im->m_type == kVideoFile ? ".jpg" : "";
    3469
    35     return m_instance;
     70    // Create the relative path and filename to the thumbnail image or thumbdir
     71    return QString("%1/%2%3").arg(THUMBNAIL_DIR, im->m_fileName, ext);
    3672}
    3773
    3874
    39 
    40 /** \fn     ImageUtils::LoadDirectoryFromDB(QMap<QString, ImageMetadata *>*)
    41  *  \brief  Loads all directory information from the database
    42  *  \param  dbList The list where the results are stored
    43  *  \return void
    44  */
    45 void ImageUtils::LoadDirectoriesFromDB(QMap<QString, ImageMetadata *>* dbList)
     75/*!
     76 \brief Return a timestamp/datestamp for an image or dir
     77 \details Uses exif timestamp if defined, otherwise file modified date
     78 \param im Image or dir
     79 \return QString Exif Timestamp text for images, file modified datestamp text
     80 for dirs and images with no exif
     81*/
     82QString ImageUtils::ImageDateOf(ImageItem *im)
    4683{
    47     dbList->clear();
    48 
    49     MSqlQuery query(MSqlQuery::InitCon());
    50     query.prepare(
    51                 QString("SELECT "
    52                         "dir_id, filename, name, path, parent_id, "
    53                         "dir_count, file_count, "
    54                         "hidden "
    55                         "FROM gallery_directories"));
    56 
    57     if (!query.exec())
    58         LOG(VB_GENERAL, LOG_ERR, MythDB::DBErrorMessage(query.lastError()));
    59 
    60     if (query.size() > 0)
    61     {
    62         while (query.next())
    63         {
    64             ImageMetadata *im = new ImageMetadata();
    65             LoadDirectoryValues(query, im);
    66             dbList->insert(im->m_fileName, im);
    67         }
    68     }
     84    return im->m_date > 0
     85            ? QDateTime::fromTime_t(im->m_date)
     86              .toString(Qt::DefaultLocaleShortDate)
     87            : QDateTime::fromTime_t(im->m_modTime).date()
     88              .toString(Qt::DefaultLocaleShortDate);
    6989}
    7090
    7191
    72 
    73 /** \fn     ImageUtils::LoadFilesFromDB(QMap<QString, ImageMetadata *>*)
    74  *  \brief  Loads all file information from the database
    75  *  \param  dbList The list where the results are stored
    76  *  \return void
    77  */
    78 void ImageUtils::LoadFilesFromDB(QMap<QString, ImageMetadata *>* dbList)
     92/*!
     93 \brief Constructor
     94*/
     95ImageSg::ImageSg()
     96    : m_hostname(gCoreContext->GetMasterHostName()),
     97      m_hostport(gCoreContext->GetMasterServerPort())
    7998{
    80     dbList->clear();
     99    m_sgImages = StorageGroup(IMAGE_STORAGE_GROUP, m_hostname, false);
     100}
    81101
    82     MSqlQuery query(MSqlQuery::InitCon());
    83     query.prepare(
    84                 QString("SELECT "
    85                         "file_id, CONCAT_WS('/', path, filename), name, path, "
    86                         "dir_id, type, modtime, size, extension, "
    87                         "angle, date, zoom, "
    88                         "hidden, orientation "
    89                         "FROM gallery_files"));
    90102
    91     if (!query.exec())
    92         LOG(VB_GENERAL, LOG_ERR, MythDB::DBErrorMessage(query.lastError()));
     103//! Storage Group singleton
     104ImageSg* ImageSg::m_instance = NULL;
    93105
    94     if (query.size() > 0)
    95     {
    96         while (query.next())
    97         {
    98             ImageMetadata *im = new ImageMetadata();
    99             LoadFileValues(query, im);
    100             dbList->insert(im->m_fileName, im);
    101         }
    102     }
    103 }
    104106
     107/*!
     108 \brief Return singleton
     109 \return ImageSg Images SG object
     110*/
     111ImageSg* ImageSg::getInstance()
     112{
     113    if (!m_instance)
     114        m_instance = new ImageSg();
     115    return m_instance;
     116}
    105117
    106118
    107 /** \fn     ImageUtils::LoadFileFromDB(ImageMetadata *, int)
    108  *  \brief  Load the file information from the database given by the id
    109  *  \param  im The image metadata which holds the information
    110  *  \return void
     119/*!
     120 * \brief Get filters for detecting recognised images/videos
     121 * \details Supported pictures are determined by QImage; supported videos
     122 * are determined from the mythplayer settings (Video Associations)
     123 * \sa http://qt-project.org/doc/qt-4.8/qimagereader.html#supportedImageFormats
     124 * \return QDir A QDir initialised with filters for detecting images/videos
    111125 */
    112 void ImageUtils::LoadFileFromDB(ImageMetadata * im, int id)
     126QDir ImageSg::GetImageFilters()
    113127{
    114     MSqlQuery query(MSqlQuery::InitCon());
    115     query.prepare(
    116                 QString("SELECT "
    117                         "file_id, CONCAT_WS('/', path, filename), name, path, dir_id, "
    118                         "type, modtime, size, extension, "
    119                         "angle, date, zoom, "
    120                         "hidden, orientation "
    121                         "FROM gallery_files "
    122                         "WHERE file_id = :FILE_ID;"));
    123     query.bindValue(":FILE_ID", id);
     128    QStringList glob;
    124129
    125     if (!query.exec())
    126         LOG(VB_GENERAL, LOG_ERR, MythDB::DBErrorMessage(query.lastError()));
     130    // Determine supported picture formats
     131    m_imageFileExt.clear();
     132    foreach (const QByteArray &ext, QImageReader::supportedImageFormats())
     133    {
     134        m_imageFileExt << QString(ext);
     135        glob << "*." + ext;
     136    }
    127137
    128     if (query.size() > 0)
     138    // Determine supported video formats
     139    m_videoFileExt.clear();
     140    const FileAssociations::association_list faList =
     141        FileAssociations::getFileAssociation().getList();
     142    for (FileAssociations::association_list::const_iterator p =
     143             faList.begin(); p != faList.end(); ++p)
    129144    {
    130         while (query.next())
     145        if (!p->use_default && p->playcommand == "Internal")
    131146        {
    132             LoadFileValues(query, im);
     147            m_videoFileExt << QString(p->extension);
     148            glob << "*." + p->extension;
    133149        }
    134150    }
    135 }
    136151
     152    QDir dir;
    137153
     154    // Apply filters to only detect image files
     155    dir.setNameFilters(glob);
     156    dir.setFilter(QDir::AllDirs | QDir::Files | QDir::Readable |
     157                  QDir::NoDotAndDotDot | QDir::NoSymLinks);
    138158
    139 /** \fn     ImageUtils::InsertDirectoryIntoDB(ImageMetadata *)
    140  *  \brief  Saves information about a given directory in the database
    141  *  \param  dm Information of the directory
    142  *  \return void
    143  */
    144 int ImageUtils::InsertDirectoryIntoDB(ImageMetadata *im)
    145 {
    146     MSqlQuery query(MSqlQuery::InitCon());
    147     query.prepare(
    148                 QString("INSERT INTO gallery_directories ("
    149                         "filename, name, path, parent_id, "
    150                         "dir_count, file_count, "
    151                         "hidden "
    152                         ") VALUES ("
    153                         ":FILENAME, :NAME, :PATH, :PARENT_ID, "
    154                         ":DIRCOUNT, :FILECOUNT, "
    155                         ":HIDDEN);"));
    156     query.bindValue(":FILENAME",    im->m_fileName);
    157     query.bindValue(":NAME",        im->m_name);
    158     query.bindValue(":PATH",        im->m_path);
    159     query.bindValue(":PARENT_ID",   im->m_parentId);
    160     query.bindValue(":DIRCOUNT" ,   im->m_dirCount);
    161     query.bindValue(":FILECOUNT",   im->m_fileCount);
    162     query.bindValue(":HIDDEN",      im->m_isHidden);
     159    // Sync files before dirs to improve thumb generation response
     160    // Order by time (oldest first) - this determines the order thumbs appear
     161    dir.setSorting(QDir::DirsLast | QDir::Time | QDir::Reversed);
    163162
    164     if (!query.exec())
    165         MythDB::DBError("Error inserting, query: ", query);
    166 
    167     return query.lastInsertId().toInt();
     163    return dir;
    168164}
    169165
    170166
    171 
    172 /** \fn     ImageUtils::InsertFileIntoDB(ImageMetadata *)
    173  *  \brief  Saves information about a given file in the database
    174  *  \param  dm Information of the file
    175  *  \return void
     167/*!
     168 * \brief Get paths for a list of images
     169 * \param images List of images
     170 * \return ImagePaths Map of image names & their paths
    176171 */
    177 int ImageUtils::InsertFileIntoDB(ImageMetadata *im)
     172QString ImageSg::GetFilePath(ImageItem *im)
    178173{
    179     MSqlQuery query(MSqlQuery::InitCon());
    180     query.prepare(
    181                 QString("INSERT INTO gallery_files ("
    182                         "filename, name, path, dir_id, "
    183                         "type, modtime, size, extension, "
    184                         "angle, date, zoom, "
    185                         "hidden, orientation "
    186                         ") VALUES ("
    187                         ":FILENAME, :NAME, :PATH, :DIR_ID, "
    188                         ":TYPE, :MODTIME, :SIZE, :EXTENSION, "
    189                         ":ANGLE, :DATE, :ZOOM, "
    190                         ":HIDDEN, :ORIENT)"));
    191     query.bindValue(":FILENAME",    im->m_fileName);
    192     query.bindValue(":NAME",        im->m_name);
    193     query.bindValue(":PATH",        im->m_path);
    194     query.bindValue(":DIR_ID",      im->m_parentId);
    195     query.bindValue(":TYPE",        im->m_type);
    196     query.bindValue(":MODTIME",     im->m_modTime);
    197     query.bindValue(":SIZE",        im->m_size);
    198     query.bindValue(":EXTENSION",   im->m_extension);
    199     query.bindValue(":ANGLE",       im->GetAngle());
    200     query.bindValue(":DATE",        im->m_date);
    201     query.bindValue(":ZOOM",        im->GetZoom());
    202     query.bindValue(":HIDDEN",      im->m_isHidden);
    203     query.bindValue(":ORIENT",      im->GetOrientation());
    204 
    205     if (!query.exec())
    206         MythDB::DBError("Error inserting, query: ", query);
    207 
    208     return query.lastInsertId().toInt();
     174    QString path = m_sgImages.FindFile(im->m_fileName);
     175    if (path.isEmpty())
     176        LOG(VB_FILE, LOG_NOTICE,
     177            QString("Image: File %1 not found in Storage Group %2")
     178            .arg(im->m_fileName).arg(IMAGE_STORAGE_GROUP));
     179    return path;
    209180}
    210181
    211182
    212 
    213 /** \fn     ImageUtils::UpdateDirectoryInDB(ImageMetadata *)
    214  *  \brief  Updates the information about a given directory in the database
    215  *  \param  dm Information of the directory
    216  *  \return void
    217  */
    218 bool ImageUtils::UpdateDirectoryInDB(ImageMetadata *im)
     183/*!
     184 \brief Moves images and dirs within the storage group (filesystem)
     185 \details Uses renaming. Files never move filesystems within the Storage Group
     186 \param images List of images/dirs to move
     187 \param parent New parent directory
     188 \return bool True if at least 1 file was moved
     189*/
     190bool ImageSg::MoveFiles(ImageList &images, ImageItem *parent)
    219191{
    220     MSqlQuery query(MSqlQuery::InitCon());
    221     query.prepare(
    222                 QString("UPDATE gallery_directories SET "
    223                         "filename =     :FILENAME, "
    224                         "name =         :NAME, "
    225                         "path =         :PATH, "
    226                         "parent_id =    :PARENT_ID, "
    227                         "dir_count =    :DIR_COUNT, "
    228                         "file_count =   :FILE_COUNT, "
    229                         "hidden =       :HIDDEN "
    230                         "WHERE dir_id = :ID;"));
    231     query.bindValue(":FILENAME",    im->m_fileName);
    232     query.bindValue(":NAME",        im->m_name);
    233     query.bindValue(":PATH",        im->m_path);
    234     query.bindValue(":PARENT_ID",   im->m_parentId);
    235     query.bindValue(":DIR_COUNT",   im->m_dirCount);
    236     query.bindValue(":FILE_COUNT",  im->m_fileCount);
    237     query.bindValue(":HIDDEN",      im->m_isHidden);
    238     query.bindValue(":ID",          im->m_id);
    239 
    240     return query.exec();
    241 }
    242 
     192    bool changed = false;
     193    foreach (const ImageItem * im, images)
     194    {
     195        // Get SG dir of this file
     196        QString sgDir = m_sgImages.FindFileDir(im->m_fileName);
     197        if (sgDir.isEmpty())
     198        {
     199            LOG(VB_FILE, LOG_NOTICE,
     200                QString("Image: File %1 not found in Storage Group %2")
     201                .arg(im->m_fileName).arg(IMAGE_STORAGE_GROUP));
     202            continue;
     203        }
    243204
     205        // Use existing fs & name with destination path
     206        QString oldPath = QString("%1/%2").arg(sgDir, im->m_fileName);
     207        QString newPath = QString("%1/%2/%3").arg(sgDir, parent->m_fileName, im->m_name);
    244208
    245 /** \fn     ImageUtils::UpdateFileInDB(ImageMetadata *)
    246  *  \brief  Updates the information about a given file in the database
    247  *  \param  dm Information of the file
    248  *  \return void
    249  */
    250 bool ImageUtils::UpdateFileInDB(ImageMetadata *im)
    251 {
    252     MSqlQuery query(MSqlQuery::InitCon());
    253     query.prepare(
    254                 QString("UPDATE gallery_files SET "
    255                         "filename       = :FILENAME, "
    256                         "name           = :NAME, "
    257                         "path           = :PATH, "
    258                         "dir_id         = :DIR_ID, "
    259                         "type           = :TYPE, "
    260                         "modtime        = :MODTIME, "
    261                         "size           = :SIZE, "
    262                         "extension      = :EXTENSION, "
    263                         "angle          = :ANGLE, "
    264                         "date           = :DATE, "
    265                         "zoom           = :ZOOM, "
    266                         "hidden         = :HIDDEN, "
    267                         "orientation    = :ORIENT "
    268                         "WHERE file_id  = :ID;"));
    269     query.bindValue(":FILENAME",    im->m_fileName);
    270     query.bindValue(":NAME",        im->m_name);
    271     query.bindValue(":PATH",        im->m_path);
    272     query.bindValue(":DIR_ID",      im->m_parentId);
    273     query.bindValue(":TYPE",        im->m_type);
    274     query.bindValue(":MODTIME",     im->m_modTime);
    275     query.bindValue(":SIZE",        im->m_size);
    276     query.bindValue(":EXTENSION",   im->m_extension);
    277     query.bindValue(":ANGLE",       im->GetAngle());
    278     query.bindValue(":DATE",        im->m_date);
    279     query.bindValue(":ZOOM",        im->GetZoom());
    280     query.bindValue(":HIDDEN",      im->m_isHidden);
    281     query.bindValue(":ORIENT",      im->GetOrientation());
    282     query.bindValue(":ID",          im->m_id);
    283 
    284     return query.exec();
     209        // Move file
     210        if (QFile::rename(oldPath, newPath))
     211        {
     212            changed = true;
     213            LOG(VB_FILE, LOG_DEBUG, QString("Image: Moved %1 -> %2")
     214                .arg(oldPath, newPath));
     215        }
     216        else
     217            LOG(VB_FILE, LOG_ERR, QString("Image: Failed to move %1 to %2")
     218                .arg(oldPath, newPath));
     219    }
     220    return changed;
    285221}
    286222
    287223
    288 
    289 /** \fn     ImageUtils::RemoveFromDB(ImageMetadata *im)
    290  *  \brief  Deletes either a directory or file from the database
    291  *  \param  im Information of the given item
    292  *  \return void
     224/*!
     225 * \brief Deletes images and dirs from the storage group (filesystem)
     226 * \details Dirs will only be deleted if empty. Files/dirs that failed
     227 * to delete will be removed from the list.
     228 * \param[in,out] images List of images/dirs to delete. On return the files that
     229 * were successfully deleted.
    293230 */
    294 bool ImageUtils::RemoveFromDB(ImageMetadata *im)
     231void ImageSg::RemoveFiles(ImageList &images)
    295232{
    296     if (!im)
    297         return false;
    298 
    299     if (im->m_type == kSubDirectory || im->m_type == kUpDirectory)
    300         return RemoveDirectoryFromDB(im);
    301 
    302     if (im->m_type == kImageFile || im->m_type == kVideoFile)
    303         return RemoveFileFromDB(im);
    304 
    305     return false;
    306 }
    307 
     233    QMutableListIterator<ImageItem *> it(images);
     234    it.toBack();
     235    while (it.hasPrevious())
     236    {
     237        ImageItem *im = it.previous();
    308238
     239        // Find file
     240        QString absFilename = m_sgImages.FindFile(im->m_fileName);
    309241
    310 /** \fn     ImageUtils::RemoveDirectoryFromDB(ImageMetadata *)
    311  *  \brief  Deletes the information about a given directory in the database
    312  *  \param  im Information of the directory
    313  *  \return void
    314  */
    315 bool ImageUtils::RemoveDirectoryFromDB(ImageMetadata *im)
    316 {
    317     if (!im)
    318         return false;
     242        if (absFilename.isEmpty())
     243        {
     244            LOG(VB_FILE, LOG_ERR,
     245                QString("Image: File %1 not found in Storage Group %2")
     246                .arg(im->m_fileName).arg(IMAGE_STORAGE_GROUP));
     247            it.remove();
     248            delete im;
     249            continue;
     250        }
    319251
    320     return RemoveDirectoryFromDB(im->m_id);
     252        // Remove file
     253        bool ok;
     254        if (im->IsFile())
     255            ok = QFile::remove(absFilename);
     256        else
     257        {
     258            QDir dir;
     259            ok = dir.rmdir(absFilename);
     260        }
     261        if (!ok)
     262        {
     263            LOG(VB_FILE, LOG_ERR, QString("Can't delete %1").arg(im->m_fileName));
     264            // Remove from list
     265            it.remove();
     266            delete im;
     267        }
     268    }
    321269}
    322270
    323271
    324 
    325 bool ImageUtils::RemoveDirectoryFromDB(int id)
    326 {
    327     MSqlQuery query(MSqlQuery::InitCon());
    328     query.prepare(
    329                 QString("DELETE from gallery_directories "
    330                         "WHERE dir_id = :ID;"));
    331     query.bindValue(":ID", id);
    332 
    333     return query.exec();
    334 }
    335 
     272// Standard query to be parsed by CreateImage
     273const QString dbQuery =
     274    "SELECT "
     275    "file_id, name, path, dir_id, type, modtime, size, extension, "
     276    "date, hidden, orientation, angle, filename FROM gallery_files "
     277    "WHERE %1 %2 %3";
    336278
    337279
    338 /** \fn     ImageUtils::RemoveFileFromDB(ImageMetadata *)
    339  *  \brief  Deletes the information about a given file in the database
    340  *  \param  im Information of the directory
    341  *  \return void
    342  */
    343 bool ImageUtils::RemoveFileFromDB(ImageMetadata *im)
     280//! Initialise static constant
     281QMap<int, QString> ImageDbReader::InitQueries()
    344282{
    345     if (!im)
    346         return false;
    347 
    348     return RemoveFileFromDB(im->m_id);
     283    QMap<int, QString> map;
     284    map.insert(kPicOnly,     QString("type = %1").arg(kImageFile));
     285    map.insert(kVideoOnly,   QString("type = %1").arg(kVideoFile));
     286    map.insert(kPicAndVideo, QString("type > %1").arg(kSubDirectory));
     287    return map;
    349288}
    350289
    351290
     291// Db query clauses to distinguish between pictures, videos & dirs
     292const QMap<int, QString> ImageDbReader::queryFiles = ImageDbReader::InitQueries();
     293const QString ImageDbReader::queryDirs = QString("type <= %1").arg(kSubDirectory);
    352294
    353 bool ImageUtils::RemoveFileFromDB(int id)
    354 {
    355     MSqlQuery query(MSqlQuery::InitCon());
    356     query.prepare(
    357                 QString("DELETE from gallery_files "
    358                         "WHERE file_id = :ID;"));
    359     query.bindValue(":ID", id);
    360295
    361     return query.exec();
    362 }
    363 
    364 
    365 
    366 /** \fn     ImageUtils::LoadDirectoryValues(MSqlQuery &, ImageMetadata *)
    367  *  \brief  Loads the directory information from the database into the dataMap
    368  *  \param  query Information from the database
    369  *  \param  dm Holds the loaded information
    370  *  \return void
     296/*!
     297 * \brief Generate Db query clause for sort order
     298 * \return QString Db clause
    371299 */
    372 void ImageUtils::LoadDirectoryValues(MSqlQuery &query, ImageMetadata *dm)
     300void ImageDbReader::SetSortOrder(int order)
    373301{
    374     dm->m_id            = query.value(0).toInt();
    375     dm->m_name          = query.value(2).toString();
    376     dm->m_path          = query.value(3).toString();
    377     dm->m_fileName      = QDir::cleanPath(QDir(dm->m_path).filePath(dm->m_name));
    378     dm->m_parentId      = query.value(4).toInt();
    379     dm->m_dirCount      = query.value(5).toInt();
    380     dm->m_fileCount     = query.value(6).toInt();
    381     dm->m_isHidden      = query.value(7).toInt();
    382 
    383     // preset all directories as subfolders
    384     dm->m_type          = kSubDirectory;
    385 
    386     LoadDirectoryThumbnailValues(dm);
    387 }
     302    m_order = order;
    388303
     304    // prepare the sorting statement
     305    switch (order)
     306    {
     307        case kSortByNameAsc:
     308            m_orderSelector = "ORDER BY name ASC"; break;
     309        case kSortByNameDesc:
     310            m_orderSelector = "ORDER BY name DESC"; break;
     311        case kSortByModTimeAsc:
     312            m_orderSelector = "ORDER BY modtime ASC, name ASC"; break;
     313        case kSortByModTimeDesc:
     314            m_orderSelector = "ORDER BY modtime DESC, name ASC"; break;
     315        case kSortByExtAsc:
     316            m_orderSelector = "ORDER BY extension ASC, name ASC"; break;
     317        case kSortByExtDesc:
     318            m_orderSelector = "ORDER BY extension DESC, name ASC"; break;
     319        case kSortBySizeAsc:
     320            m_orderSelector = "ORDER BY size ASC, name ASC"; break;
     321        case kSortBySizeDesc:
     322            m_orderSelector = "ORDER BY size DESC, name ASC"; break;
     323        case kSortByDateAsc:
     324            m_orderSelector = "ORDER BY date ASC, name ASC"; break;
     325        case kSortByDateDesc:
     326            m_orderSelector = "ORDER BY date DESC, name ASC"; break;
     327        case kSortByNone:
     328        default:
     329            m_orderSelector = "";
     330    }
     331}
    389332
    390333
    391 /** \fn     ImageUtils::LoadFileValues(MSqlQuery &, ImageMetadata *)
    392  *  \brief  Loads the file information from the database into the dataMap
    393  *  \param  query Information from the database
    394  *  \param  dm Holds the loaded information
    395  *  \return void
     334/*!
     335 * \brief Create image metadata
     336 * \param query Db query result
     337 * \return ImageItem An image
    396338 */
    397 void ImageUtils::LoadFileValues(MSqlQuery &query, ImageMetadata *dm)
     339ImageItem *ImageDbReader::CreateImage(MSqlQuery &query)
    398340{
    399     dm->m_id            = query.value(0).toInt();
    400     dm->m_name          = query.value(2).toString();
    401     dm->m_path          = query.value(3).toString();
    402     dm->m_fileName      = QDir::cleanPath(QDir(dm->m_path).filePath(dm->m_name));
    403     dm->m_parentId      = query.value(4).toInt();
    404     dm->m_type          = query.value(5).toInt();
    405     dm->m_modTime       = query.value(6).toInt();
    406     dm->m_size          = query.value(7).toInt();
    407     dm->m_extension     = query.value(8).toString();
    408     dm->SetAngle(         query.value(9).toInt());
    409     dm->m_date          = query.value(10).toInt();
    410     dm->SetZoom(          query.value(11).toInt(), true);
    411     dm->m_isHidden      = query.value(12).toInt();
    412     dm->SetOrientation(   query.value(13).toInt(), true);
    413 
    414     LoadFileThumbnailValues(dm);
     341    ImageItem *im = new ImageItem();
     342
     343    im->m_id            = query.value(0).toInt();
     344    im->m_name          = query.value(1).toString();
     345    im->m_path          = query.value(2).toString();
     346    im->m_parentId      = query.value(3).toInt();
     347    im->m_type          = query.value(4).toInt();
     348    im->m_modTime       = query.value(5).toInt();
     349    im->m_size          = query.value(6).toInt();
     350    im->m_extension     = query.value(7).toString();
     351    im->m_date          = query.value(8).toUInt();
     352    im->m_isHidden      = query.value(9).toInt();
     353    im->m_orientation   = query.value(10).toInt();
     354    im->m_userThumbnail = query.value(11).toInt();
     355    im->m_comment       = query.value(12).toString();
     356    im->m_fileName      =
     357        QDir::cleanPath(QDir(im->m_path).filePath(im->m_name));
     358
     359    im->m_thumbPath = ImageUtils::ThumbPathOf(im);
     360
     361    return im;
    415362}
    416363
    417364
    418 
    419 /** \fn     ImageUtils::GetStorageDirs()
    420  *  \brief  Gets the available storage groups
    421  *  \return List of all available storage groups
     365/*!
     366 * \brief Read database images/dirs as list
     367 * \details Get selected database items (mixed files/dirs) in prescribed order,
     368 * optionally including currently hidden items.
     369 * \param[out] images List of images/dirs from Db
     370 * \param[in] selector Db selection query clause
     371 * \param[in] showAll If true, all items are extracted. Otherwise only items matching
     372 * the visibility filter are returned
     373 * \param[in] ordered If true, returned lists are ordered according to GallerySortOrder
     374 * setting. Otherwise they are in undefined (database) order.
     375 * \return int Number of items matching query.
    422376 */
    423 QStringList ImageUtils::GetStorageDirs()
     377int ImageDbReader::ReadDbItems(ImageList &images, QString selector,
     378                               bool showAll, bool ordered)
    424379{
    425     QStringList sgDirList;
     380    QString   orderSelect  = ordered ? m_orderSelector : "";
     381    QString   hiddenSelect = showAll || m_showHidden ? "" : "AND hidden = 0";
    426382
    427     QString sgName = IMAGE_STORAGE_GROUP;
    428 
    429     if (!sgName.isEmpty())
     383    MSqlQuery query(MSqlQuery::InitCon());
     384    query.prepare(dbQuery.arg(selector, hiddenSelect, orderSelect));
     385    if (!query.exec())
    430386    {
    431         QString host = gCoreContext->GetHostName();
    432 
    433         // Search for the specified dirs in the defined storage group.
    434         // If there is no such storage group then don't use the fallback
    435         // and don't get the default storage group name of "/mnt/store".
    436         // The list will be empty. The user has to check the settings.
    437         StorageGroup sg;
    438         sg.Init(sgName, host, false);
    439         sgDirList = sg.GetDirList();
     387        LOG(VB_GENERAL, LOG_ERR, MythDB::DBErrorMessage(query.lastError()));
     388        return 0;
    440389    }
    441390
    442     return sgDirList;
    443 }
    444 
    445 
     391    while (query.next())
     392    {
     393        ImageItem *im = CreateImage(query);
    446394
    447 /** \fn     ImageUtils::LoadDirectoryData(QFileInfo &, DataMap *, int)
    448  *  \brief  Loads the information from the fileInfo into the dataMap object
    449  *  \param  fileInfo Holds the information about the file
    450  *  \param  data Holds the loaded information about a file
    451  *  \param  parentId The parent directory
    452  *  \return void
    453  */
    454 void ImageUtils::LoadDirectoryData(QFileInfo &fileInfo,
    455                                    ImageMetadata *data,
    456                                    int parentId,
    457                                    const QString &baseDirectory)
    458 {
    459     QDir dir(baseDirectory);
    460     data->m_parentId    = parentId;
    461     data->m_fileName    = dir.relativeFilePath(fileInfo.absoluteFilePath());
    462     data->m_name        = fileInfo.fileName();
    463     data->m_path        = dir.relativeFilePath(fileInfo.absolutePath());
    464     data->m_isHidden    = fileInfo.isHidden();
    465 
    466     dir.cd(data->m_fileName);
    467     data->m_dirCount = dir.entryList(QDir::Dirs |
    468                                      QDir::NoSymLinks |
    469                                      QDir::NoDotAndDotDot |
    470                                      QDir::Readable).count();
    471 
    472     data->m_fileCount = dir.entryList(QDir::Files |
    473                                       QDir::NoSymLinks |
    474                                       QDir::NoDotAndDotDot |
    475                                       QDir::Readable).count();
     395        // Initialise image thumbnail
     396        if (im->IsFile())
     397        {
     398            im->m_thumbNails.append(im->m_thumbPath);
     399            im->m_thumbIds.append(im->m_id);
     400        }
     401        images.append(im);
     402    }
     403    return query.size();
    476404}
    477405
    478406
    479 /** \fn     ImageUtils::LoadFileData(QFileInfo &, DataMap *)
    480  *  \brief  Loads the information from the fileInfo into the dataMap object
    481  *  \param  fileInfo Holds the information about the file
    482  *  \param  data Holds the loaded information about a file
    483  *  \return void
     407/*!
     408 * \brief Read sub-tree of database images and dirs as lists
     409 * \details Returns database files and dirs contained in the sub-tree of
     410 * specific dirs. Optionally ordered and including currently hidden items.
     411 * \param[out] files List of images within sub-tree
     412 * \param[out] dirs List of dirs within sub-tree
     413 * \param[in] idList Comma-separated list of parent dir ids
     414 * \param[in] showAll If true, all items are extracted. Otherwise only items matching
     415 * the visibility filter are returned
     416 * \param[in] ordered If true, returned lists are ordered according to GallerySortOrder
     417 * setting. Otherwise they are in undefined (database) order.
    484418 */
    485 void ImageUtils::LoadFileData(QFileInfo &fileInfo,
    486                               ImageMetadata *data,
    487                               const QString &baseDirectory)
     419void ImageDbReader::ReadDbTree(ImageList &files,
     420                            ImageList &dirs,
     421                            QStringList idList,
     422                            bool showAll,
     423                            bool ordered)
    488424{
    489     QDir baseDir(baseDirectory);
    490     data->m_fileName    = baseDir.relativeFilePath(fileInfo.absoluteFilePath());
    491     data->m_name        = fileInfo.fileName();
    492     data->m_path        = baseDir.relativeFilePath(fileInfo.absolutePath());
    493     data->m_modTime     = fileInfo.lastModified().toTime_t();
    494     data->m_size        = fileInfo.size();
    495     data->m_isHidden    = fileInfo.isHidden();
    496     data->m_extension   = fileInfo.completeSuffix().toLower();
    497 
    498     // Set defaults, the data will be loaded later
    499     data->SetAngle(0);
    500     data->m_date = MAX_UTCTIME;
    501 
    502     if (m_imageFileExt.contains(data->m_extension))
    503     {
    504         data->m_type = kImageFile;
    505     }
    506     else if (m_videoFileExt.contains(data->m_extension))
     425    // Load starting files
     426    QString ids = idList.join(",");
     427    ReadDbFilesById(files, ids, showAll, ordered);
     428    ReadDbDirsById(dirs, ids, showAll, ordered);
     429
     430    // Add all descendants
     431    ImageList subdirs;
     432    while (!idList.isEmpty())
    507433    {
    508         data->m_type = kVideoFile;
    509     }
    510     else
    511     {
    512         data->m_type = kUnknown;
     434        QString selector =
     435            QString("dir_id IN (%1) AND %2").arg(idList.join(","));
     436        ReadDbItems(files, selector.arg(queryFiles[m_showType]), showAll, ordered);
     437        ReadDbItems(subdirs, selector.arg(queryDirs), showAll, ordered);
     438        dirs += subdirs;
     439        idList.clear();
     440        foreach (const ImageItem * im, subdirs)
     441        {
     442            if (im->IsDirectory())
     443                idList.append(QString::number(im->m_id));
     444        }
     445        subdirs.clear();
    513446    }
    514447}
    515448
    516449
    517 
    518 /** \fn     ImageUtils::GetExifOrientation(const QString &fileName, bool *ok)
    519  *  \brief  Reads and returns the orientation value
    520  *  \param  fileName The filename that holds the exif data
    521  *  \param  ok Will be set to true if the reading was ok, otherwise false
    522  *  \return The orientation value
     450/*!
     451 * \brief Read database images and dirs as map
     452 * \details Returns selected database items, separated into files and dirs. Results contain
     453 * no thumbnails (paths nor urls) and are mapped to item name and thus unordered.
     454 * \param[out] files Map of image names and metadata from Db
     455 * \param[out] dirs Map of dir names and metadata from Db
     456 * \param[in] selector Db selection query
    523457 */
    524 int ImageUtils::GetExifOrientation(const QString &fileName, bool *ok)
     458void ImageDbWriter::ReadDbItems(ImageMap &files, ImageMap &dirs,
     459                                const QString &selector)
    525460{
    526     QString tag = "Exif.Image.Orientation";
    527     QString value = GetExifValue(fileName, tag, ok);
    528 
    529     // The orientation of the image. Only return the value if its valid
    530     // See http://jpegclub.org/exif_orientation.html for details
    531     bool valid;
    532     int orientation = QString(value).toInt(&valid);
    533     return (valid) ? orientation : 1;
    534 }
    535 
     461    MSqlQuery query(MSqlQuery::InitCon());
     462    query.prepare(dbQuery.arg(selector, "", ""));
    536463
     464    if (!query.exec())
     465    {
     466        LOG(VB_GENERAL, LOG_ERR, MythDB::DBErrorMessage(query.lastError()));
     467        return;
     468    }
    537469
    538 /** \fn     ImageUtils::SetExifOrientation(const QString &, const long , bool *)
    539  *  \brief  Saves the given value in the orientation exif tag
    540  *  \param  fileName The filename that holds the exif data
    541  *  \param  orientation The value that shall be saved in the exif data
    542  *  \param  ok Will be set to true if the update was ok, otherwise false
    543  *  \return void
    544  */
    545 void ImageUtils::SetExifOrientation(const QString &fileName,
    546                                     const int orientation, bool *ok)
    547 {
    548     // the orientation of the image.
    549     // See http://jpegclub.org/exif_orientation.html for details
    550     if (orientation >= 1 && orientation <= 8)
     470    while (query.next())
    551471    {
    552         QString tag = "Exif.Image.Orientation";
    553         SetExifValue(fileName, tag, QString::number(orientation), ok);
     472        ImageItem *im = CreateImage(query);
     473
     474        if (im->IsDirectory())
     475            dirs.insert(im->m_fileName, im);
     476        else
     477            files.insert(im->m_fileName, im);
    554478    }
    555479}
    556480
    557481
    558 
    559 /** \fn     ImageUtils::GetExifDate(const QString &, bool *)
    560  *  \brief  Reads and returns the exif date
    561  *  \param  fileName The filename that holds the exif data
    562  *  \param  ok Will be set to true if the reading was ok, otherwise false
    563  *  \return The date in utc time
     482/*!
     483 * \brief Clear image database
     484 * \note Only backends should modify the database
    564485 */
    565 long ImageUtils::GetExifDate(const QString &fileName, bool *ok)
     486void ImageDbWriter::ClearDb()
    566487{
    567     long utcTime = MAX_UTCTIME;
    568 
    569     QString tag = "Exif.Image.DateTime";
    570     QString value = GetExifValue(fileName, tag, ok);
    571 
    572     // convert the string into the UTC time. We need to split
    573     // the exif time format, which is this: "2006:07:21 18:54:58"
    574     if (!value.isEmpty())
    575     {
    576         bool ok;
    577         QDateTime dateTime =
    578                 QDateTime(QDate(value.mid(0,4).toInt(&ok, 10),
    579                                 value.mid(5,2).toInt(&ok, 10),
    580                                 value.mid(8,2).toInt(&ok, 10)),
    581                           QTime(value.mid(11,2).toInt(&ok, 10),
    582                                 value.mid(14,2).toInt(&ok, 10),
    583                                 value.mid(17,2).toInt(&ok, 10), 0));
    584 
    585         // convert it to the utc time so
    586         // we can easily compare it later.
    587         utcTime = dateTime.toTime_t();
    588     }
     488    MSqlQuery query(MSqlQuery::InitCon());
     489    query.prepare(QString("TRUNCATE gallery_files;"));
    589490
    590     return utcTime;
     491    if (!query.exec())
     492        LOG(VB_GENERAL, LOG_ERR, MythDB::DBErrorMessage(query.lastError()));
    591493}
    592494
    593495
    594 
    595 /** \fn     ImageUtils::SetExifDate(const QString &, const long , bool *)
    596  *  \brief  Saves the given date in the date exif tag
    597  *  \param  fileName The filename that holds the exif data
    598  *  \param  date The date that shall be saved in the exif data
    599  *  \param  ok Will be set to true if the update was ok, otherwise false
    600  *  \return void
     496/*!
     497 * \brief Adds new dir to database.
     498 * \details Dir should not already exist.
     499 * \param im Image data for dir
     500 * \return int Database id for new dir
     501 * \note Only backends should modify the database
    601502 */
    602 void ImageUtils::SetExifDate(const QString &fileName,
    603                              const long date, bool *ok)
     503int ImageDbWriter::InsertDbDirectory(ImageItem &im)
    604504{
    605     QString value;
     505    MSqlQuery query(MSqlQuery::InitCon());
     506    query.prepare("INSERT INTO gallery_files ("
     507                  "name, path, dir_id, type, modtime, hidden "
     508                  ") VALUES ("
     509                  ":NAME, :PATH, :PARENT_ID, :TYPE, :MODTIME, :HIDDEN);");
     510    query.bindValue(":NAME", im.m_name);
     511    query.bindValue(":PATH", im.m_path);
     512    query.bindValue(":PARENT_ID", im.m_parentId);
     513    query.bindValue(":TYPE", im.m_type);
     514    query.bindValue(":MODTIME", im.m_modTime);
     515    query.bindValue(":HIDDEN", im.m_isHidden);
    606516
    607     // Convert the date number into the UTC time and then
    608     // into the string with the format "2006:07:21 18:54:58".
    609     QDateTime dateTime;
    610     dateTime.setTime_t(date);
    611     value = dateTime.toString("yyyy:MM:dd hh:mm:ss");
     517    if (!query.exec())
     518        MythDB::DBError("Error inserting, query: ", query);
    612519
    613     if (!value.isEmpty())
    614     {
    615         QString tag = "Exif.Image.DateTime";
    616         SetExifValue(fileName, tag, value, ok);
    617     }
     520    return query.lastInsertId().toInt();
    618521}
    619522
    620523
    621 
    622 /** \fn     ImageUtils::GetAllExifValues(const QString &)
    623  *  \brief  Reads and returns all non empty tags from the given file
    624  *  \param  fileName The filename that holds the exif data
    625  *  \return The list of exif tag names and values
     524/*!
     525 * \brief Updates or creates database image or dir
     526 * \details Item does not need to pre-exist
     527 * \sa ImageUtils::InsertDbDirectory
     528 * \param im Image or dir
     529 * \return bool False if db update failed
     530 * \note Only backends should modify the database
    626531 */
    627 QList<QStringList> ImageUtils::GetAllExifValues(const QString &fileName)
     532bool ImageDbWriter::UpdateDbFile(ImageItem *im)
    628533{
    629     // default value, an empty list means no
    630     // exif tags were found or an error occured
    631     QList<QStringList> valueList;
    632 
    633     try
    634     {
    635         Exiv2::Image::AutoPtr image =
    636                 Exiv2::ImageFactory::open(fileName.toLocal8Bit().constData());
    637 
    638         if (image.get())
    639         {
    640             image->readMetadata();
    641             Exiv2::ExifData &exifData = image->exifData();
    642             if (!exifData.empty())
    643             {
    644                 LOG(VB_FILE, LOG_DEBUG,
    645                     QString("Found %1 tag(s) for file %2")
    646                     .arg(exifData.count())
    647                     .arg(fileName));
    648 
    649                 Exiv2::ExifData::const_iterator end = exifData.end();
    650                 Exiv2::ExifData::const_iterator i = exifData.begin();
    651                 for (; i != end; ++i)
    652                 {
    653                     QString value = QString::fromStdString(i->value().toString());
    654 
    655                     // Do not add empty tags to the list
    656                     if (!value.isEmpty())
    657                     {
    658                         QStringList values;
    659 
    660                         // These three are the same as i->key()
    661                         values.append(QString::fromStdString(i->familyName()));
    662                         values.append(QString::fromStdString(i->groupName()));
    663                         values.append(QString::fromStdString(i->tagName()));
    664                         values.append(QString::fromStdString(i->key()));
    665                         values.append(QString::fromStdString(i->tagLabel()));
    666                         values.append(QString::fromStdString(i->value().toString()));
    667 
    668                         LOG(VB_FILE, LOG_DEBUG, QString("%1 (Type %2)")
    669                             .arg(values.join(" : "))
    670                             .arg(i->typeName()));
    671 
    672                         // Add the exif information to the list, ignoring empty labels
    673                         if (!values.at(4).isEmpty())
    674                             valueList.append(values);
    675                     }
    676                 }
    677             }
    678             else
    679             {
    680                 LOG(VB_FILE, LOG_DEBUG,
    681                     QString("Exiv2 error: No header, file %1")
    682                     .arg(fileName));
    683             }
    684         }
    685         else
    686         {
    687             LOG(VB_GENERAL, LOG_ERR,
    688                 QString("Exiv2 error: Could not open file, file %1")
    689                 .arg(fileName));
    690         }
    691     }
    692     catch (Exiv2::Error& e)
    693     {
    694         LOG(VB_GENERAL, LOG_ERR,
    695             QString("Exiv2 exception %1, file %2")
    696             .arg(e.what()).arg(fileName));
    697     }
    698 
    699     return valueList;
     534    MSqlQuery query(MSqlQuery::InitCon());
     535    query.prepare(QString("REPLACE INTO gallery_files ("
     536                          "file_id, name, path, dir_id, type, "
     537                          "modtime, size, extension, date, orientation, filename) "
     538                          "VALUES ("
     539                          ":ID, :NAME, :PATH, :DIR, :TYPE, :MODTIME, "
     540                          ":SIZE, :EXT, :DATE, :ORIENT, :COMMENT )"));
     541    query.bindValue(":ID", im->m_id);
     542    query.bindValue(":NAME", im->m_name);
     543    query.bindValue(":PATH", im->m_path.isNull() ? "" : im->m_path);
     544    query.bindValue(":DIR", im->m_parentId);
     545    query.bindValue(":TYPE", im->m_type);
     546    query.bindValue(":MODTIME", im->m_modTime);
     547    query.bindValue(":SIZE", im->m_size);
     548    query.bindValue(":EXT", im->m_extension);
     549    query.bindValue(":DATE", im->m_date);
     550    query.bindValue(":ORIENT", im->m_orientation);
     551    query.bindValue(":COMMENT", im->m_comment.isNull() ? "" : im->m_comment);
     552    // hidden & user thumb will be preserved for existing files
     553    // & initialised using db defaults (0/false/not set) for new files
     554
     555    bool ok = query.exec();
     556    if (!ok)
     557        MythDB::DBError("Error updating, query: ", query);
     558    return ok;
    700559}
    701560
    702561
    703 
    704 /** \fn     ImageUtils::GetExifValue(const QString &, const QString &, bool *)
    705  *  \brief  Reads and returns the value of an exif tag in a file
    706  *  \param  fileName The filename that holds the exif data
    707  *  \param  exifTag The key that shall be updated
    708  *  \param  ok Will be set to true if the reading was ok, otherwise false
    709  *  \return The value of the exif tag or an empty string
     562/*!
     563 * \brief Remove images/dirs from database
     564 * \details Item does not need to exist in db
     565 * \param imList List of items to delete
     566 * \return QStringList List of item ids that were successfully removed
     567 * \note Only backends should modify the database
    710568 */
    711 QString ImageUtils::GetExifValue(const QString &fileName,
    712                                  const QString &exifTag,
    713                                  bool *ok)
     569QStringList ImageDbWriter::RemoveFromDB(const ImageList imList)
    714570{
    715     // Assume the exif reading fails
    716     *ok = false;
    717 
    718     // default value
    719     QString value("");
    720 
    721     try
     571    QStringList ids;
     572    if (!imList.isEmpty())
    722573    {
    723         Exiv2::Image::AutoPtr image =
    724                 Exiv2::ImageFactory::open(fileName.toLocal8Bit().constData());
     574        foreach (const ImageItem * im, imList)
     575            ids << QString::number(im->m_id);
    725576
    726         if (image.get())
    727         {
    728             image->readMetadata();
    729             Exiv2::ExifData &exifData = image->exifData();
    730             if (!exifData.empty())
    731             {
    732                 Exiv2::Exifdatum &datum =
    733                         exifData[exifTag.toLocal8Bit().constData()];
    734 
    735                 value = QString::fromStdString(datum.toString());
    736                 if (!value.isEmpty())
    737                 {
    738                     *ok = true;
    739                 }
    740                 else
    741                 {
    742                     LOG(VB_FILE, LOG_DEBUG,
    743                         QString("Exiv2 error: No tag found, file %1, tag %2")
    744                         .arg(fileName).arg(exifTag));
    745                 }
    746             }
    747             else
    748             {
    749                 LOG(VB_FILE, LOG_DEBUG,
    750                     QString("Exiv2 error: No header, file %1, tag %2")
    751                     .arg(fileName).arg(exifTag));
    752             }
    753         }
    754         else
     577        MSqlQuery query(MSqlQuery::InitCon());
     578        query.prepare(QString("DELETE IGNORE FROM gallery_files "
     579                              "WHERE file_id IN (%2);")
     580                      .arg(ids.join(",")));
     581
     582        if (!query.exec())
    755583        {
    756             LOG(VB_GENERAL, LOG_ERR,
    757                 QString("Exiv2 error: Could not open file, file %1, tag %2")
    758                 .arg(fileName).arg(exifTag));
     584            MythDB::DBError("Error deleting, query: ", query);
     585            return QStringList();
    759586        }
    760587    }
    761     catch (Exiv2::Error& e)
    762     {
    763         LOG(VB_GENERAL, LOG_ERR,
    764             QString("Exiv2 exception %1, file %2, tag %3")
    765             .arg(e.what()).arg(fileName).arg(exifTag));
    766     }
    767 
    768     return value;
     588    return ids;
    769589}
    770590
    771591
    772 
    773 /** \fn     ImageUtils::SetExifValue(const QString &, const QString &, const QString &, bool *)
    774  *  \brief  Updates the exif tag in a file with the given value
    775  *  \param  fileName The filename that holds the exif data
    776  *  \param  exifTag The key that shall be updated
    777  *  \param  value The new value of the exif tag
    778  *  \param  ok Will be set to true if the update was ok, otherwise false
    779  *  \return True if the exif key exists, otherwise false
     592/*!
     593 * \brief Sets hidden status of an image/dir in database
     594 * \param hide True = hidden, False = unhidden
     595 * \param ids List of item ids
     596 * \return bool False if db update failed
     597 * \note Only backends should modify the database
    780598 */
    781 void ImageUtils::SetExifValue(const QString &fileName,
    782                               const QString &exifTag,
    783                               const QString &value,
    784                               bool *ok)
     599bool ImageDbWriter::SetHidden(bool hide, QStringList &ids)
    785600{
    786     // Assume the exif writing fails
    787     *ok = false;
     601    MSqlQuery query(MSqlQuery::InitCon());
    788602
    789     try
     603    if (!ids.isEmpty())
    790604    {
    791         Exiv2::Image::AutoPtr image =
    792                 Exiv2::ImageFactory::open(fileName.toLocal8Bit().constData());
    793 
    794         if (image.get())
     605        query.prepare(QString("UPDATE gallery_files SET "
     606                              "hidden = :HIDDEN "
     607                              "WHERE file_id IN (%1);").arg(ids.join(",")));
     608        query.bindValue(":HIDDEN", hide ? 1 : 0);
     609\
     610        if (!query.exec())
    795611        {
    796             image->readMetadata();
    797             Exiv2::ExifData &exifData = image->exifData();
    798 
    799             Exiv2::Exifdatum &datum = exifData[exifTag.toLocal8Bit().constData()];
    800             datum.setValue(value.toLocal8Bit().constData());
    801 
    802             image->writeMetadata();
    803 
    804             *ok = true;
    805         }
    806         else
    807         {
    808             LOG(VB_GENERAL, LOG_ERR,
    809                 QString("Exiv2 error: Could not open file, file %1, tag %2")
    810                 .arg(fileName).arg(exifTag));
     612            MythDB::DBError("Error updating, query: ", query);
     613            return false;
    811614        }
    812615    }
    813     catch (Exiv2::Error& e)
    814     {
    815         LOG(VB_GENERAL, LOG_ERR,
    816             QString("Exiv2 exception %1, file %2, tag %3, value %4")
    817             .arg(e.what()).arg(fileName).arg(exifTag).arg(value));
    818     }
     616    return true;
    819617}
    820618
    821619
    822 
    823 /** \fn     ImageUtils::HasExifKey(Exiv2::ExifData, const QString &)
    824  *  \brief  Checks if the exif data of the file contains the given key
    825  *  \param  exifData The entire exif data
    826  *  \param  exifTag The key that shall be checked
    827  *  \return True if the exif key exists, otherwise false
     620/*!
     621 * \brief Assign the thumbnails to be used for a dir in database
     622 * \param dir Dir id
     623 * \param id Image or dir id to use as cover/thumbnail
     624 * \note Only backends should modify the database
    828625 */
    829 bool ImageUtils::HasExifKey(Exiv2::ExifData exifData,
    830                             const QString &exifTag)
     626void ImageDbWriter::SetCover(int dir, int id)
    831627{
    832     Exiv2::ExifKey key(exifTag.toLocal8Bit().constData());
    833     Exiv2::ExifData::iterator it = exifData.findKey(key);
     628    MSqlQuery query(MSqlQuery::InitCon());
    834629
    835     // If the iterator has is the end of the
    836     // list then the key has not been found
    837     return !(it == exifData.end());
     630    query.prepare("UPDATE gallery_files SET "
     631                  "angle = :COVER "
     632                  "WHERE file_id = :DIR");
     633    query.bindValue(":COVER", id);
     634    query.bindValue(":DIR", dir);
     635    \
     636    if (!query.exec())
     637        MythDB::DBError("Error updating, query: ", query);
    838638}
    839639
    840 /**
    841  *  \brief  Gets four images from the directory from the
    842  *          database which will be used as a folder thumbnail
    843  *  \param  im Holds the loaded information
    844  *  \return void
    845  */
    846 void ImageUtils::LoadDirectoryThumbnailValues(ImageMetadata *im)
     640
     641void ImageDbWriter::SetOrientation(ImageItem *im)
    847642{
    848     // Try to get four new thumbnail filenames
    849     // from the available images in this folder
    850643    MSqlQuery query(MSqlQuery::InitCon());
    851     query.prepare("SELECT CONCAT_WS('/', path, name), path FROM gallery_files "
    852                           "WHERE path = :PATH "
    853                           "AND type = :TYPE "
    854                           "AND hidden = '0' LIMIT :LIMIT");
    855     query.bindValue(":PATH", im->m_fileName);
    856     query.bindValue(":TYPE", kImageFile);
    857     query.bindValue(":LIMIT", kMaxFolderThumbnails);
    858644
     645    query.prepare("UPDATE gallery_files SET "
     646                  "orientation = :ORIENTATION "
     647                  "WHERE file_id = :ID");
     648    query.bindValue(":ORIENTATION", im->m_orientation);
     649    query.bindValue(":ID", im->m_id);
     650    \
    859651    if (!query.exec())
    860         LOG(VB_GENERAL, LOG_ERR, MythDB::DBErrorMessage(query.lastError()));
    861 
    862     int i = 0;
    863     while (query.next())
    864     {
    865         QString thumbFileName = QString("%1%2")
    866                 .arg(GetConfDir().append("/tmp/MythImage/"))
    867                 .arg(query.value(0).toString());
    868 
    869         if (i >= im->m_thumbFileNameList->size())
    870             break;
    871 
    872         im->m_thumbFileNameList->replace(i, thumbFileName);
    873         im->m_thumbPath = query.value(1).toString();
    874         ++i;
    875     }
    876 
    877     // Set the path to the thumbnail files. As a default this will be
    878     // the path ".mythtv/MythGallery" in the users home directory
    879     im->m_thumbPath.prepend(GetConfDir().append("/tmp/MythImage/"));
     652        MythDB::DBError("Error updating, query: ", query);
    880653}
    881654
    882655
    883 
    884 /**
    885  *  \brief  Sets the thumbnail information for a file
    886  *  \param  im Holds the loaded information
    887  *  \return void
    888  */
    889 void ImageUtils::LoadFileThumbnailValues(ImageMetadata *im)
    890 {
    891     // Set the path to the thumbnail files. As a default this will be
    892     // the path ".mythtv/MythGallery" in the users home directory
    893     im->m_thumbPath = im->m_path;
    894     im->m_thumbPath.prepend(GetConfDir().append("/tmp/MythImage/"));
    895 
    896     // Create the full path and filename to the thumbnail image
    897     QString thumbFileName = QString("%1%2")
    898                                 .arg(GetConfDir().append("/tmp/MythImage/"))
    899                                 .arg(im->m_fileName);
    900 
    901     // If the file is a video then append a png, otherwise the preview
    902     // image would not be readable due to the video file extension
    903     if (im->m_type == kVideoFile)
    904         thumbFileName.append(".png");
    905 
    906     im->m_thumbFileNameList->replace(0, thumbFileName);
    907 }
    908 
  • mythtv/libs/libmythmetadata/imageutils.h

    diff --git a/mythtv/libs/libmythmetadata/imageutils.h b/mythtv/libs/libmythmetadata/imageutils.h
    index e130a79..1077b4c 100644
    a b  
     1//! \file
     2//! \brief Provides access to database and storage group files for images
     3//! \details Encapsulates (most) database and storage group dependencies
     4
    15#ifndef IMAGEUTILS_H
    26#define IMAGEUTILS_H
    37
    48// Qt headers
    5 #include <QDirIterator>
    6 
    7 // Other headers
    8 // Note: Older versions of Exiv2 don't have the exiv2.hpp include
    9 // file.  Using image.hpp instead seems to work.
    10 //#include <exiv2/exiv2.hpp>
    11 #ifdef _MSC_VER
    12 #include <exiv2/src/image.hpp>
    13 #else
    14 #include <exiv2/image.hpp>
    15 #endif
     9#include <QDir>
     10#include <QMap>
    1611
    1712// MythTV headers
    18 #include "mythdbcon.h"
    19 #include "imagemetadata.h"
    20 #include "mythmetaexp.h"
     13#include <mythcontext.h>
     14#include <mythdbcon.h>
     15#include <mythmetaexp.h>
     16#include <storagegroup.h>
     17
     18
     19// Builtin storage groups
     20#define IMAGE_STORAGE_GROUP         "Photographs"
     21#define THUMBNAIL_STORAGE_GROUP     "Temp"
     22// Filesystem dir used by TEMP SG
     23#define TEMP_DIR                    "tmp"
     24// Subdir within BE Myth tmp to be used for thumbnails
     25#define THUMBNAIL_DIR               IMAGE_STORAGE_GROUP
     26
     27// Id of the root node representing the Storage Group
     28#define ROOT_DB_ID 1
     29
     30//! Image ordering
     31enum ImageSortOrder {
     32    kSortByNone        = 0, //!< Undefined order
     33    kSortByNameAsc     = 1, //!< Name A-Z
     34    kSortByNameDesc    = 2, //!< Name Z-A
     35    kSortByModTimeAsc  = 3, //!< File modified time Earliest - Latest
     36    kSortByModTimeDesc = 4, //!< File modified time Latest - Earliest
     37    kSortByExtAsc      = 5, //!< Extension A-Z
     38    kSortByExtDesc     = 6, //!< Extension Z-A
     39    kSortBySizeAsc     = 7, //!< File size Smallest - Largest
     40    kSortBySizeDesc    = 8, //!< File size Largest - Smallest
     41    kSortByDateAsc     = 9, //!< Exif date Earliest - Latest
     42    kSortByDateDesc    = 10 //!< Exif date Latest - Earliest
     43};
     44
     45//! Type of image to display
     46enum ImageDisplayType {
     47    kPicAndVideo = 0, //!< Show Pictures & Videos
     48    kPicOnly     = 1, //!< Hide videos
     49    kVideoOnly   = 2  //!< Hide pictures
     50};
     51
     52
     53//! Type of image node
     54// We need to use other names to avoid getting coflicts with the videolist.h file
     55enum ImageNodeType {
     56    kUnknown        = 0, //!< Shouldn't occur
     57    kBaseDirectory  = 1, //!< "Gallery" root node of all images
     58    kUpDirectory    = 2, //!< Dir/Parent currently viewed
     59    kSubDirectory   = 3, //!< Child sub-dirs
     60    kImageFile      = 4, //!< A picture
     61    kVideoFile      = 5  //!< A video
     62};
     63
     64
     65//! Represents a picture, video or directory
     66class META_PUBLIC ImageItem
     67{
     68public:
     69    ImageItem();
     70    ImageItem(const ImageItem &);
     71
     72    // Db key
     73    int         m_id;            //!< Uniquely identifies an image or dir. Assigned by db
     74
     75    // Db File attributes
     76    QString     m_name;          //!< File/Dir name (no path)
     77    QString     m_path;          //!< Path relative to storage group
     78    int         m_parentId;      //!< Id of parent dir
     79    int         m_type;          //!< Type of node
     80    int         m_modTime;       //!< Filesystem modified datestamp
     81    int         m_size;          //!< Filesize (images only)
     82    QString     m_extension;     //!< File extension (images only)
     83    uint32_t    m_date;          //!< Image creation date, from metadata
     84    int         m_orientation;   //!< Camera orientation, from metadata
     85    QString     m_comment;       //!< User comment, from metadata
     86
     87    // Db User attributes
     88    bool        m_isHidden;      //!< If true, image won't be shown
     89    int         m_userThumbnail; //!< Id of an image/dir to use as thumbnail (dirs only)
     90
     91    // Derived attributes
     92    QString     m_fileName;      //!< File path relative to storage group
     93    QString     m_thumbPath;     //!< Path of thumbnail, relative to BE cache
     94    QStringList m_thumbNails;    //!< BE URLs to use for thumbnails
     95    QList<int>  m_thumbIds;      //!< Image ids corresponding to above thumbnails
     96    int         m_dirCount;      //!< Number of child sub-dirs (dirs only)
     97    int         m_fileCount;     //!< Number of child images (dirs only)
     98
     99    bool IsDirectory()  const { return m_type <= kSubDirectory; }
     100    bool IsFile()       const { return m_type >  kSubDirectory; }
     101};
    21102
    22 #define IMAGE_STORAGE_GROUP "Photographs";
     103Q_DECLARE_METATYPE(ImageItem*)
    23104
     105
     106// Convenience containers
     107typedef QMap<QString, ImageItem *>  ImageMap;
     108typedef QList<ImageItem *>          ImageList;
     109typedef QList<int>                  ImageIdList;
     110
     111
     112//! General functions
    24113class META_PUBLIC ImageUtils
    25114{
    26115public:
    27     static ImageUtils* getInstance();
    28116
    29     void LoadDirectoriesFromDB(QMap<QString, ImageMetadata *>*);
    30     void LoadFilesFromDB(QMap<QString, ImageMetadata *>*);
    31     void LoadFileFromDB(ImageMetadata * im, int id);
     117    static QString ThumbPathOf(ImageItem *);
     118    static QString ImageDateOf(ImageItem *);
     119};
    32120
    33     int  InsertDirectoryIntoDB(ImageMetadata *);
    34     int  InsertFileIntoDB(ImageMetadata *);
    35121
    36     bool UpdateDirectoryInDB(ImageMetadata *);
    37     bool UpdateFileInDB(ImageMetadata *);
     122//! Wrapped Images Storage Group providing filesystem access
     123class META_PUBLIC ImageSg
     124{
     125public:
     126    static ImageSg *getInstance();
     127    QDir            GetImageFilters();
     128    QString         GetFilePath(ImageItem*);
     129    QStringList     GetStorageDirs() { return m_sgImages.GetDirList(); }
    38130
    39     bool RemoveFromDB(ImageMetadata *);
    40     bool RemoveDirectoryFromDB(ImageMetadata *);
    41     bool RemoveDirectoryFromDB(int);
    42     bool RemoveFileFromDB(ImageMetadata *);
    43     bool RemoveFileFromDB(int);
     131    //! Generate URL of a thumbnail
     132    QString         GenerateThumbUrl(const QString &path)
     133    { return gCoreContext->GenMythURL(m_hostname, m_hostport, path, THUMBNAIL_STORAGE_GROUP);}
    44134
    45     void LoadDirectoryData(QFileInfo &, ImageMetadata *, int, const QString &);
    46     void LoadFileData(QFileInfo &, ImageMetadata *, const QString &);
     135    //! Generate URL of an image
     136    QString         GenerateUrl(const QString &path)
     137    { return gCoreContext->GenMythURL(m_hostname, m_hostport, path, IMAGE_STORAGE_GROUP); }
    47138
    48     QStringList  GetStorageDirs();
     139    //! Determine type from an extension
     140    ImageNodeType   GetImageType(const QString &ext)
     141    {
     142        return m_imageFileExt.contains(ext)
     143                ? kImageFile
     144                : m_videoFileExt.contains(ext) ? kVideoFile : kUnknown;
     145    }
    49146
    50     long                GetExifDate(const QString &, bool *);
    51     int                 GetExifOrientation(const QString &, bool *);
    52     QString             GetExifValue(const QString &, const QString &, bool *);
    53     QList<QStringList>  GetAllExifValues(const QString &fileName);
     147    bool MoveFiles(ImageList &, ImageItem *parent);
     148    void RemoveFiles(ImageList &);
    54149
    55     void    SetExifDate(const QString &, const long, bool *);
    56     void    SetExifOrientation(const QString &, const int, bool *);
    57     void    SetExifValue(const QString &, const QString &, const QString &, bool *);
     150    //! Images storage group
     151    StorageGroup       m_sgImages;
    58152
    59153private:
    60     ImageUtils();
    61     ~ImageUtils();
    62     static ImageUtils   *m_instance;
     154    // A singleton as creating SGs is laborious
     155    ImageSg();
     156    static ImageSg *m_instance;
     157    QString         m_hostname, m_hostport;
     158
     159    //! List of file extensions recognised as pictures
     160    QStringList    m_imageFileExt;
     161    //! List of file extensions recognised as videos
     162    QStringList    m_videoFileExt;
     163
     164};
     165
     166
     167//! Provides read-only access to image database (for FE/clients).
     168//! Returned items are optionally filtered and sorted
     169class META_PUBLIC ImageDbReader
     170{
     171public:
     172    ImageDbReader(int order, bool showAll, int showType)
     173        : m_showHidden(showAll), m_showType(showType) { SetSortOrder(order); }
     174
     175    void SetSortOrder(int order);
     176    int  GetSortOrder()                 { return m_order; }
     177    void SetVisibility(bool showHidden) { m_showHidden = showHidden; }
     178    bool GetVisibility()                { return m_showHidden; }
     179    void SetType(int showType)          { m_showType = showType; }
     180    int  GetType()                      { return m_showType; }
     181
     182    int ReadDbItems(ImageList &images,
     183                    QString selector,
     184                    bool showAll = true,
     185                    bool ordered = false);
     186
     187    /*!
     188     * \brief Read selected database images/dirs by id
     189     * \details Returns database items (mixed files/dirs) selected by id with options
     190     * for sort order and including currently hidden items.
     191     * \param[out] images List of images/dirs from Db
     192     * \param[in] ids Comma-separated list of image ids
     193     * \param[in] showAll If true, all items are extracted. Otherwise only items matching
     194     * the visibility filter are returned
     195     * \param[in] ordered If true, returned lists are ordered according to GallerySortOrder
     196     * setting. Otherwise they are in undefined (database) order.
     197     * \param[in] selector Db selection query clause
     198     * \return int Number of items matching query.
     199     */
     200    int ReadDbItemsById(ImageList &images,
     201                        const QString &ids,
     202                        bool showAll = true,
     203                        bool ordered = false,
     204                        const QString &selector = "TRUE")
     205    {
     206        QString idSelector = QString("file_id IN (%1) AND %2").arg(ids, selector);
     207        return ReadDbItems(images, idSelector, showAll, ordered);
     208    }
     209
     210    /*!
     211     * \brief Read selected database images (no dirs) by id
     212     * \details Returns database images selected by id with options
     213     * for sort order and including currently hidden items.
     214     * \param[out] files List of images from Db
     215     * \param[in] ids Comma-separated list of image ids
     216     * \param[in] showAll If true, all items are extracted. Otherwise only items matching
     217     * the visibility filter are returned
     218     * \param[in] ordered If true, returned lists are ordered according to GallerySortOrder
     219     * setting. Otherwise they are in undefined (database) order.
     220     * \return int Number of items matching query.
     221     */
     222    int ReadDbFilesById(ImageList &files,
     223                        const QString &ids,
     224                        bool showAll = true,
     225                        bool ordered = false)
     226    { return ReadDbItemsById(files, ids, showAll, ordered, queryFiles[m_showType]); }
     227
     228    /*!
     229     * \brief Read selected database dirs (no images) by id
     230     * \details Returns database dirs selected by id with options
     231     * for sort order and including currently hidden items.
     232     * \param[out] dirs List of dirs from Db
     233     * \param[in] ids Comma-separated list of image ids
     234     * \param[in] showAll If true, all items are extracted. Otherwise only items matching
     235     * the visibility filter are returned
     236     * \param[in] ordered If true, returned lists are ordered according to GallerySortOrder
     237     * setting. Otherwise they are in undefined (database) order.
     238     * \return int Number of items matching query.
     239     */
     240    int ReadDbDirsById(ImageList &dirs,
     241                       const QString &ids,
     242                       bool showAll = true,
     243                       bool ordered = false)
     244    { return ReadDbItemsById(dirs, ids, showAll, ordered, queryDirs); }
     245
     246    /*!
     247     * \brief Read database images (no dirs) that are children of dirs
     248     * \details Returns database images that are direct children of specific dirs with
     249     * options for sort order and including currently hidden items.
     250     * \param[out] files List of images from Db
     251     * \param[in] ids Comma-separated list of parent dir ids
     252     * \param[in] showAll If true, all items are extracted. Otherwise only items matching
     253     * the visibility filter are returned
     254     * \param[in] ordered If true, returned lists are ordered according to GallerySortOrder
     255     * setting. Otherwise they are in undefined (database) order.
     256     * \return int Number of items matching query.
     257     */
     258    int ReadDbChildFiles(ImageList &files,
     259                         const QString &ids,
     260                         bool showAll = true,
     261                         bool ordered = false)
     262    {
     263        QString selector = QString("dir_id IN (%1) AND %2").arg(ids, queryFiles[m_showType]);
     264        return ReadDbItems(files, selector, showAll, ordered);
     265    }
     266
     267    /*!
     268     * \brief Read database dirs (no images) that are children of dirs
     269     * \details Returns database dirs that are direct sub-directories of specific dirs with
     270     * options for sort order and including currently hidden items.
     271     * \param[out] dirs List of sub-dirs from Db
     272     * \param[in] ids Comma-separated list of parent dir ids
     273     * \param[in] showAll If true, all items are extracted. Otherwise only items matching
     274     * the visibility filter are returned
     275     * \param[in] ordered If true, returned lists are ordered according to GallerySortOrder
     276     * setting. Otherwise they are in undefined (database) order.
     277     * \return int Number of items matching query.
     278     */
     279    int ReadDbChildDirs(ImageList &dirs,
     280                        const QString &ids,
     281                        bool showAll = true,
     282                        bool ordered = false)
     283    {
     284        QString selector = QString("dir_id IN (%1) AND %2").arg(ids, queryDirs);
     285        return ReadDbItems(dirs, selector, showAll, ordered);
     286    }
     287
     288    void ReadDbTree(ImageList &files,
     289                    ImageList &dirs,
     290                    QStringList ids,
     291                    bool showAll = true,
     292                    bool ordered = false);
     293
     294protected:
     295    ImageItem *CreateImage(MSqlQuery &query);
     296
     297    //! Db query clauses to distinguish between images & dirs
     298    static const QString queryDirs;
     299    static const QMap<int, QString> queryFiles;
     300    static QMap<int, QString> InitQueries();
     301
     302    //! Filter for hidden files
     303    bool m_showHidden;
     304    //! Filter for pictures/videos
     305    int m_showType;
     306    //! Sort order for returned items
     307    int m_order;
     308    //! SQL clause for sort order
     309    QString m_orderSelector;
     310};
     311
     312
     313//! Provides read-write access to image database (for backends only).
     314//! Returned items are not filtered or sorted
     315class META_PUBLIC ImageDbWriter : private ImageDbReader
     316{
     317public:
     318
     319    ImageDbWriter() : ImageDbReader(kSortByNone, true, kPicAndVideo) {}
     320
     321    void ReadDbItems(ImageMap &files,
     322                     ImageMap &dirs,
     323                     const QString &selector = "TRUE");
     324
     325    //! \sa ImageDbReader::ReadDbItems
     326    int ReadDbItems(ImageList &images, const QString &selector)
     327    { return ImageDbReader::ReadDbItems(images, selector, true, false); }
     328
     329    //! \sa ImageDbReader::ReadDbItemsById
     330    int ReadDbItemsById(ImageList &images,
     331                        const QString &ids,
     332                        const QString &selector = "TRUE")
     333    { return ReadDbItems(images, QString("file_id IN (%1) AND %2").arg(ids, selector)); }
     334
     335    //! \sa ImageDbReader::ReadDbFilesById
     336    int ReadDbFilesById(ImageList &files, const QString &ids)
     337    { return ReadDbItemsById(files, ids, queryFiles[kPicAndVideo]); }
     338
     339    //! \sa ImageDbReader::ReadDbDirsById
     340    int ReadDbDirsById(ImageList &dirs, const QString &ids)
     341    { return ReadDbItemsById(dirs, ids, queryDirs); }
    63342
    64     QStringList          m_imageFileExt;
    65     QStringList          m_videoFileExt;
     343    //! \sa ImageDbReader::ReadDbChildFiles
     344    int ReadDbChildFiles(ImageList &files, const QString &ids)
     345    { return ImageDbReader::ReadDbChildFiles(files, ids, true, false); }
    66346
    67     void LoadDirectoryValues(MSqlQuery &, ImageMetadata *);
    68     void LoadFileValues(MSqlQuery &, ImageMetadata *);
     347    //! \sa ImageDbReader::ReadDbChildDirs
     348    int ReadDbChildDirs(ImageList &dirs, const QString &ids)
     349    { return ImageDbReader::ReadDbChildDirs(dirs, ids, true, false); }
    69350
    70     void LoadDirectoryThumbnailValues(ImageMetadata *);
    71     void LoadFileThumbnailValues(ImageMetadata *);
     351    //! \sa ImageDbReader::ReadDbTree
     352    void ReadDbTree(ImageList &files,
     353                    ImageList &dirs,
     354                    QStringList ids)
     355    { ImageDbReader::ReadDbTree(files, dirs, ids, true, false); }
    72356
    73     bool HasExifKey(Exiv2::ExifData, const QString &);
     357    void        ClearDb();
     358    int         InsertDbDirectory(ImageItem &);
     359    bool        UpdateDbFile(ImageItem *);
     360    QStringList RemoveFromDB(const ImageList);
     361    bool        SetHidden(bool hide, QStringList &);
     362    void        SetCover(int dir, int id);
     363    void        SetOrientation(ImageItem *im);
    74364};
    75365
    76366#endif // IMAGEUTILS_H
  • mythtv/libs/libmythmetadata/libmythmetadata.pro

    diff --git a/mythtv/libs/libmythmetadata/libmythmetadata.pro b/mythtv/libs/libmythmetadata/libmythmetadata.pro
    index bfc83b3..1a2e8aa 100644
    a b HEADERS += mythuiimageresults.h 
    2525HEADERS += musicmetadata.h musicutils.h metaio.h metaiotaglib.h
    2626HEADERS += metaioflacvorbis.h metaioavfcomment.h metaiomp4.h
    2727HEADERS += metaiowavpack.h metaioid3.h metaiooggvorbis.h
    28 HEADERS += imagemetadata.h imageutils.h imagescan.h imagescanthread.h
    29 HEADERS += imagethumbgenthread.h musicfilescanner.h metadatagrabber.h
     28HEADERS += imagemetadata.h imageutils.h imagescanner.h
     29HEADERS += imagethumbs.h musicfilescanner.h metadatagrabber.h
    3030
    3131SOURCES += cleanup.cpp  dbaccess.cpp  dirscan.cpp  globals.cpp
    3232SOURCES += parentalcontrols.cpp  videoscan.cpp  videoutils.cpp
    SOURCES += mythuiimageresults.cpp 
    3737SOURCES += musicmetadata.cpp musicutils.cpp metaio.cpp metaiotaglib.cpp
    3838SOURCES += metaioflacvorbis.cpp metaioavfcomment.cpp metaiomp4.cpp
    3939SOURCES += metaiowavpack.cpp metaioid3.cpp metaiooggvorbis.cpp
    40 SOURCES += imagemetadata.cpp imageutils.cpp imagescan.cpp imagescanthread.cpp
    41 SOURCES += imagethumbgenthread.cpp musicfilescanner.cpp metadatagrabber.cpp
     40SOURCES += imagemetadata.cpp imageutils.cpp imagescanner.cpp
     41SOURCES += imagethumbs.cpp musicfilescanner.cpp metadatagrabber.cpp
    4242
    4343INCLUDEPATH += ../libmythbase ../libmythtv
    4444INCLUDEPATH += ../.. ../ ./ ../libmythui
    inc.files += musicmetadata.h musicutils.h 
    9292inc.files += metaio.h metaiotaglib.h
    9393inc.files += metaioflacvorbis.h metaioavfcomment.h metaiomp4.h
    9494inc.files += metaiowavpack.h metaioid3.h metaiooggvorbis.h
    95 inc.files += imagemetadata.h imageutils.h imagescan.h imagescanthread.h
    96 inc.files += imagethumbgenthread.h musicfilescanner.h metadatagrabber.h
     95inc.files += imagemetadata.h imageutils.h imagescanner.h
     96inc.files += imagethumbs.h musicfilescanner.h metadatagrabber.h
    9797
    9898INSTALLS += inc
    9999
  • mythtv/libs/libmythservicecontracts/datacontracts/imageMetadataInfo.h

    diff --git a/mythtv/libs/libmythservicecontracts/datacontracts/imageMetadataInfo.h b/mythtv/libs/libmythservicecontracts/datacontracts/imageMetadataInfo.h
    index f32dbb1..fab5147 100644
    a b class SERVICE_PUBLIC ImageMetadataInfo : public QObject 
    1717    Q_CLASSINFO( "version"    , "1.00" )
    1818
    1919    Q_PROPERTY( int             Number      READ Number         WRITE setNumber     )
    20     Q_PROPERTY( QString         Family      READ Family         WRITE setFamily     )
    21     Q_PROPERTY( QString         Group       READ Group          WRITE setGroup      )
     20//    Q_PROPERTY( QString         Family      READ Family         WRITE setFamily     )
     21//    Q_PROPERTY( QString         Group       READ Group          WRITE setGroup      )
    2222    Q_PROPERTY( QString         Tag         READ Tag            WRITE setTag        )
    23     Q_PROPERTY( QString         Key         READ Key            WRITE setKey        )
     23//    Q_PROPERTY( QString         Key         READ Key            WRITE setKey        )
    2424    Q_PROPERTY( QString         Label       READ Label          WRITE setLabel      )
    2525    Q_PROPERTY( QString         Value       READ Value          WRITE setValue      )
    2626
    2727    PROPERTYIMP    ( int        , Number       )
    28     PROPERTYIMP    ( QString    , Family       )
    29     PROPERTYIMP    ( QString    , Group        )
     28//    PROPERTYIMP    ( QString    , Family       )
     29//    PROPERTYIMP    ( QString    , Group        )
    3030    PROPERTYIMP    ( QString    , Tag          )
    31     PROPERTYIMP    ( QString    , Key          )
     31//    PROPERTYIMP    ( QString    , Key          )
    3232    PROPERTYIMP    ( QString    , Label        )
    3333    PROPERTYIMP    ( QString    , Value        )
    3434
    class SERVICE_PUBLIC ImageMetadataInfo : public QObject 
    5252        void Copy( const ImageMetadataInfo &src )
    5353        {
    5454            m_Number    = src.m_Number;
    55             m_Family    = src.m_Family;
    56             m_Group     = src.m_Group;
     55//            m_Family    = src.m_Family;
     56//            m_Group     = src.m_Group;
    5757            m_Tag       = src.m_Tag;
    58             m_Key       = src.m_Key;
     58//            m_Key       = src.m_Key;
    5959            m_Label     = src.m_Label;
    6060            m_Value     = src.m_Value;
    6161        }
  • new file mythtv/libs/libmythservicecontracts/services/hackimageServices.h

    diff --git a/mythtv/libs/libmythservicecontracts/services/hackimageServices.h b/mythtv/libs/libmythservicecontracts/services/hackimageServices.h
    new file mode 100644
    index 0000000..e5c08c7
    - +  
     1#ifndef IMAGESERVICES_H_
     2#define IMAGESERVICES_H_
     3
     4#include <QFileInfo>
     5#include <QStringList>
     6
     7#include "service.h"
     8#include "datacontracts/imageMetadataInfoList.h"
     9#include "datacontracts/imageSyncInfo.h"
     10
     11
     12
     13class SERVICE_PUBLIC ImageServices : public Service
     14{
     15    Q_OBJECT
     16    Q_CLASSINFO( "version"    , "2.0" )
     17//    Q_CLASSINFO( "SetImageInfo_Method",             "POST" )
     18    Q_CLASSINFO( "RemoveImageFromDB_Method",        "POST" )
     19    Q_CLASSINFO( "RemoveImage_Method",              "POST" )
     20    Q_CLASSINFO( "RenameImage_Method",              "POST" )
     21    Q_CLASSINFO( "StartSync_Method",                "POST" )
     22    Q_CLASSINFO( "StopSync_Method",                 "POST" )
     23    Q_CLASSINFO( "CreateThumbnail_Method",          "POST" )
     24
     25    public:
     26
     27        // Must call InitializeCustomTypes for each unique
     28        // Custom Type used in public slots below.
     29        ImageServices( QObject *parent = 0 ) : Service( parent )
     30        {
     31            // Must call InitializeCustomTypes for each
     32            // unique Custom Type used in public slots below.
     33            DTC::ImageMetadataInfoList::InitializeCustomTypes();
     34            DTC::ImageSyncInfo::InitializeCustomTypes();
     35        }
     36
     37    public slots:
     38
     39//        virtual bool                        SetImageInfo                ( int   Id,
     40//                                                                          const QString &Tag,
     41//                                                                          const QString &Value ) = 0;
     42
     43//        virtual bool                        SetImageInfoByFileName      ( const QString &FileName,
     44//                                                                          const QString &Tag,
     45//                                                                          const QString &Value ) = 0;
     46
     47        virtual QString                     GetImageInfo                ( int   Id,
     48                                                                          const QString &Tag ) = 0;
     49
     50//        virtual QString                     GetImageInfoByFileName      ( const QString &FileName,
     51//                                                                          const QString &Tag ) = 0;
     52
     53        virtual DTC::ImageMetadataInfoList* GetImageInfoList            ( int   Id ) = 0;
     54
     55//        virtual DTC::ImageMetadataInfoList* GetImageInfoListByFileName  ( const QString &FileName ) = 0;
     56
     57//        virtual bool                        RemoveImageFromDB  ( int   Id ) = 0;
     58        virtual bool                        RemoveImage        ( int   Id ) = 0;
     59        virtual bool                        RenameImage        ( int Id,
     60                                                                 const QString &NewName ) = 0;
     61
     62        virtual bool                        StartSync          ( void ) = 0;
     63        virtual bool                        StopSync           ( void ) = 0;
     64        virtual DTC::ImageSyncInfo*         GetSyncStatus      ( void ) = 0;
     65
     66        virtual bool                        CreateThumbnail    ( int  Id,
     67                                                                 bool Recreate) = 0;
     68};
     69
     70#endif
  • mythtv/libs/libmythservicecontracts/services/imageServices.h

    diff --git a/mythtv/libs/libmythservicecontracts/services/imageServices.h b/mythtv/libs/libmythservicecontracts/services/imageServices.h
    index c706c94..201defb 100644
    a b class SERVICE_PUBLIC ImageServices : public Service 
    1515    Q_OBJECT
    1616    Q_CLASSINFO( "version"    , "2.0" )
    1717    Q_CLASSINFO( "SetImageInfo_Method",             "POST" )
    18     Q_CLASSINFO( "RemoveImageFromDB_Method",        "POST" )
    1918    Q_CLASSINFO( "RemoveImage_Method",              "POST" )
    2019    Q_CLASSINFO( "RenameImage_Method",              "POST" )
    2120    Q_CLASSINFO( "StartSync_Method",                "POST" )
    class SERVICE_PUBLIC ImageServices : public Service 
    5453
    5554        virtual DTC::ImageMetadataInfoList* GetImageInfoListByFileName  ( const QString &FileName ) = 0;
    5655
    57         virtual bool                        RemoveImageFromDB  ( int   Id ) = 0;
    5856        virtual bool                        RemoveImage        ( int   Id ) = 0;
    5957        virtual bool                        RenameImage        ( int Id,
    6058                                                                 const QString &NewName ) = 0;
    class SERVICE_PUBLIC ImageServices : public Service 
    6361        virtual bool                        StopSync           ( void ) = 0;
    6462        virtual DTC::ImageSyncInfo*         GetSyncStatus      ( void ) = 0;
    6563
    66         virtual bool                        CreateThumbnail    ( int  Id,
    67                                                                  bool Recreate) = 0;
     64        virtual bool                        CreateThumbnail    ( int  Id ) = 0;
    6865};
    6966
    7067#endif
  • mythtv/libs/libmythui/libmythui.pro

    diff --git a/mythtv/libs/libmythui/libmythui.pro b/mythtv/libs/libmythui/libmythui.pro
    index f98e6cb..45732d7 100644
    a b HEADERS += mythuiexp.h mythuisimpletext.h mythuistatetracker.h 
    4343HEADERS += mythuianimation.h mythuiscrollbar.h
    4444HEADERS += mythnotificationcenter.h mythnotificationcenter_private.h
    4545HEADERS += mythuicomposite.h mythnotification.h mythuidefines.h
     46HEADERS += mythuimultifilebrowser.h
    4647
    4748SOURCES  = mythmainwindow.cpp mythpainter.cpp mythimage.cpp mythrect.cpp
    4849SOURCES += myththemebase.cpp  mythpainter_qimage.cpp mythpainter_yuva.cpp
    SOURCES += mythdisplay.cpp mythuivideo.cpp mythudplistener.cpp 
    6364SOURCES += mythuisimpletext.cpp mythuistatetracker.cpp
    6465SOURCES += mythuianimation.cpp mythuiscrollbar.cpp
    6566SOURCES += mythnotificationcenter.cpp mythnotification.cpp
    66 SOURCES += mythuicomposite.cpp
     67SOURCES += mythuicomposite.cpp mythuimultifilebrowser.cpp
    6768SOURCES += mythuiwebbrowser.cpp
    6869
    6970inc.path = $${PREFIX}/include/mythtv/libmythui/
    inc.files += mythuieditbar.h mythuifilebrowser.h mythuivideo.h 
    8384inc.files += mythuiexp.h mythuisimpletext.h mythuiactions.h
    8485inc.files += mythuistatetracker.h mythuianimation.h mythuiscrollbar.h
    8586inc.files += mythnotificationcenter.h mythnotification.h mythuicomposite.h
     87inc.files += mythuimultifilebrowser.h
    8688
    8789INSTALLS += inc
    8890
  • mythtv/libs/libmythui/mythuifilebrowser.cpp

    diff --git a/mythtv/libs/libmythui/mythuifilebrowser.cpp b/mythtv/libs/libmythui/mythuifilebrowser.cpp
    index 45a37b7..af690b8 100644
    a b MythUIFileBrowser::MythUIFileBrowser(MythScreenStack *parent, 
    156156        m_backButton(NULL),     m_homeButton(NULL),
    157157        m_previewImage(NULL),   m_infoText(NULL),
    158158        m_filenameText(NULL),   m_fullpathText(NULL),
    159         m_retObject(NULL)
     159        m_retObject(NULL),      m_widgetName("MythFileBrowser")
    160160{
    161161    SetPath(startPath);
    162162
    void MythUIFileBrowser::SetPath(const QString &startPath) 
    210210
    211211bool MythUIFileBrowser::Create()
    212212{
    213     if (!CopyWindowFromBase("MythFileBrowser", this))
     213    if (!CopyWindowFromBase(m_widgetName, this))
    214214        return false;
    215215
    216216    m_fileList = dynamic_cast<MythUIButtonList *>(GetChild("filelist"));
  • mythtv/libs/libmythui/mythuifilebrowser.h

    diff --git a/mythtv/libs/libmythui/mythuifilebrowser.h b/mythtv/libs/libmythui/mythuifilebrowser.h
    index 7b16822..6a83695 100644
    a b class MUI_PUBLIC MythUIFileBrowser : public MythScreenType 
    8080    MythUIFileBrowser(MythScreenStack *parent, const QString &startPath);
    8181   ~MythUIFileBrowser();
    8282
    83     bool Create(void);
     83    virtual bool Create(void);
    8484
    8585    void SetReturnEvent(QObject *retobject, const QString &resultid);
    8686
    8787    void SetTypeFilter(QDir::Filters filter) { m_typeFilter = filter; }
    8888    void SetNameFilter(QStringList filter) { m_nameFilter = filter; }
    8989
    90   private slots:
    91     void OKPressed(void);
     90  protected slots:
     91    virtual void OKPressed(void);
    9292    void cancelPressed(void);
    93     void backPressed(void);
    94     void homePressed(void);
     93    virtual void backPressed(void);
     94    virtual void homePressed(void);
    9595    void editLostFocus(void);
    9696    void PathSelected(MythUIButtonListItem *item);
    97     void PathClicked(MythUIButtonListItem *item);
     97    virtual void PathClicked(MythUIButtonListItem *item);
    9898    void LoadPreview(void);
    9999
    100   private:
     100  protected:
    101101    void SetPath(const QString &startPath);
    102102    bool GetRemoteFileList(const QString &url, const QString &sgDir,
    103103                           QStringList &list);
    104     void updateFileList(void);
     104    virtual void updateFileList(void);
    105105    void updateRemoteFileList(void);
    106106    void updateLocalFileList(void);
    107107    void updateSelectedList(void);
    class MUI_PUBLIC MythUIFileBrowser : public MythScreenType 
    136136
    137137    QObject           *m_retObject;
    138138    QString            m_id;
     139    QString            m_widgetName;
    139140};
    140141
    141142#endif
  • new file mythtv/libs/libmythui/mythuimultifilebrowser.cpp

    diff --git a/mythtv/libs/libmythui/mythuimultifilebrowser.cpp b/mythtv/libs/libmythui/mythuimultifilebrowser.cpp
    new file mode 100644
    index 0000000..6c6b4d8
    - +  
     1#include "mythuimultifilebrowser.h"
     2
     3#include <QCoreApplication>
     4
     5#include <mythlogging.h>
     6#include "mythdialogbox.h"
     7#include "mythuibuttonlist.h"
     8#include "mythuibutton.h"
     9#include "mythuitext.h"
     10
     11
     12/*!
     13 \brief Constructor
     14 \param parent Parent window
     15 \param startPath Dir to start browsing
     16*/
     17MythUIMultiFileBrowser::MythUIMultiFileBrowser(MythScreenStack *parent,
     18                                               const QString &startPath)
     19    : MythUIFileBrowser(parent, startPath),
     20      m_selectButton(NULL),
     21      m_clearButton(NULL),
     22      m_selectCount(NULL)
     23{
     24    m_widgetName = "MythMultiFileBrowser";
     25}
     26
     27
     28/*!
     29 \brief Create dialog
     30 \return bool False if dialog couldn't be created
     31*/
     32bool MythUIMultiFileBrowser::Create()
     33{
     34    if (!MythUIFileBrowser::Create())
     35        return false;
     36
     37    // Add selection buttons & selection count
     38    m_selectButton = dynamic_cast<MythUIButton *>(GetChild("selectall"));
     39    m_clearButton  = dynamic_cast<MythUIButton *>(GetChild("clearall"));
     40    m_selectCount  = dynamic_cast<MythUIText *>(GetChild("selectcount"));
     41
     42    if (!m_selectButton || !m_clearButton)
     43    {
     44        LOG(VB_GENERAL, LOG_ERR, "MythUIMultiFileBrowser: Your theme is missing"
     45            " some UI elements! Bailing out.");
     46        return false;
     47    }
     48
     49    connect(m_selectButton, SIGNAL(Clicked()), SLOT(selectPressed()));
     50    connect(m_clearButton, SIGNAL(Clicked()), SLOT(clearPressed()));
     51
     52    return true;
     53}
     54
     55
     56/*!
     57 \brief Selects/deselects a file
     58 \param item Button clicked
     59*/
     60void MythUIMultiFileBrowser::PathClicked(MythUIButtonListItem *item)
     61{
     62    if (!item)
     63        return;
     64
     65    MFileInfo finfo = item->GetData().value<MFileInfo>();
     66
     67    if (finfo.isFile())
     68    {
     69        QString name = finfo.absoluteFilePath();
     70
     71        // toggle selected state
     72        if (m_selected.remove(name))
     73        {
     74            item->setChecked(MythUIButtonListItem::NotChecked);
     75        }
     76        else
     77        {
     78            m_selected.insert(name);
     79            item->setChecked(MythUIButtonListItem::FullChecked);
     80        }
     81
     82        // Update selection stats
     83        if (m_selectCount)
     84            m_selectCount->SetText(QString::number(m_selected.size()));
     85    }
     86
     87    if (!finfo.isDir())
     88        return;
     89
     90    // clear selections on every directory change
     91    m_selected.clear();
     92
     93    MythUIFileBrowser::PathClicked(item);
     94}
     95
     96
     97/*!
     98 \brief Handle Back button
     99*/
     100void MythUIMultiFileBrowser::backPressed()
     101{
     102    m_selected.clear();
     103    MythUIFileBrowser::backPressed();
     104}
     105
     106
     107/*!
     108 \brief Handle Home button
     109*/
     110void MythUIMultiFileBrowser::homePressed()
     111{
     112    m_selected.clear();
     113    MythUIFileBrowser::homePressed();
     114}
     115
     116
     117/*!
     118 \brief Handle Accept button
     119*/
     120void MythUIMultiFileBrowser::OKPressed()
     121{
     122    if (m_retObject)
     123    {
     124        QStringList selectedPaths = m_selected.toList();
     125        DialogCompletionEvent *dce = new DialogCompletionEvent(m_id, 0, "",
     126                                                               selectedPaths);
     127        QCoreApplication::postEvent(m_retObject, dce);
     128    }
     129    Close();
     130}
     131
     132
     133/*!
     134 \brief Handle Select All
     135*/
     136void MythUIMultiFileBrowser::selectPressed()
     137{
     138    // Select all files
     139    for (int i=0; i < m_fileList->GetCount(); ++i)
     140    {
     141        MythUIButtonListItem *btn = m_fileList->GetItemAt(i);
     142        MFileInfo finfo = btn->GetData().value<MFileInfo>();
     143
     144        if (finfo.isFile())
     145            m_selected.insert(finfo.absoluteFilePath());
     146    }
     147
     148    updateFileList();
     149}
     150
     151
     152/*!
     153 \brief Handle Clear button
     154*/
     155void MythUIMultiFileBrowser::clearPressed()
     156{
     157    m_selected.clear();
     158    updateFileList();
     159}
     160
     161
     162/*!
     163 \brief Populates dialog
     164*/
     165void MythUIMultiFileBrowser::updateFileList()
     166{
     167    MythUIFileBrowser::updateFileList();
     168
     169    // Make buttonlist checkable & set selections
     170    for (int i=0; i < m_fileList->GetCount(); ++i)
     171    {
     172        MythUIButtonListItem *btn = m_fileList->GetItemAt(i);
     173        MFileInfo finfo = btn->GetData().value<MFileInfo>();
     174        btn->setCheckable(true);
     175        bool marked = m_selected.contains(finfo.absoluteFilePath());
     176        btn->setChecked(marked ? MythUIButtonListItem::FullChecked
     177                               : MythUIButtonListItem::NotChecked);
     178    }
     179
     180    // Update selection stats
     181    if (m_selectCount)
     182        m_selectCount->SetText(QString::number(m_selected.size()));
     183}
  • new file mythtv/libs/libmythui/mythuimultifilebrowser.h

    diff --git a/mythtv/libs/libmythui/mythuimultifilebrowser.h b/mythtv/libs/libmythui/mythuimultifilebrowser.h
    new file mode 100644
    index 0000000..ad54897
    - +  
     1//! \file
     2//! \brief File browser allowing multiple selections
     3
     4#ifndef MYTHUIMULTIFILEBROWSER_H
     5#define MYTHUIMULTIFILEBROWSER_H
     6
     7#include <QSet>
     8
     9#include "mythuifilebrowser.h"
     10
     11
     12//! File browser allowing multiple selections
     13class MUI_PUBLIC MythUIMultiFileBrowser : public MythUIFileBrowser
     14{
     15    Q_OBJECT
     16public:
     17    MythUIMultiFileBrowser(MythScreenStack *parent, const QString &startPath);
     18
     19    bool Create(void);
     20
     21protected slots:
     22    void OKPressed(void);
     23    void backPressed(void);
     24    void homePressed(void);
     25    void selectPressed(void);
     26    void clearPressed(void);
     27    void PathClicked(MythUIButtonListItem *item);
     28
     29protected:
     30    void updateFileList(void);
     31
     32    QSet<QString> m_selected;
     33    MythUIButton  *m_selectButton;
     34    MythUIButton  *m_clearButton;
     35    MythUIText    *m_selectCount;
     36};
     37
     38#endif // MYTHUIMULTIFILEBROWSER_H
  • new file mythtv/programs/mythbackend/imagehandlers.cpp

    diff --git a/mythtv/programs/mythbackend/imagehandlers.cpp b/mythtv/programs/mythbackend/imagehandlers.cpp
    new file mode 100644
    index 0000000..48281bc
    - +  
     1#include "imagehandlers.h"
     2
     3#include "imageutils.h"
     4#include "imagemetadata.h"
     5#include "imagescanner.h"
     6#include "imagethumbs.h"
     7
     8
     9/*!
     10 \brief Change name of an image/dir
     11 \details Renames image/dir in Photographs storage group, synchronises image database
     12 and thumbnail cache and notifies clients. A new thumbnail will be generated by next client
     13 request.
     14 \param id File/dir id
     15 \param newBase New filename
     16 \return QStringList Error message or "OK"
     17*/
     18QStringList ImageHandler::HandleRename(QString id, QString newBase)
     19{
     20    // Sanity check new name
     21    if (newBase.isEmpty() || newBase.contains("/") || newBase.contains("\\"))
     22        return QStringList("ERROR") << "Invalid name";
     23
     24    // Find image in DB
     25    ImageDbWriter db;
     26    ImageList images, dirs;
     27    db.ReadDbItemsById(images, id);
     28
     29    // Either single id not found or multiple comma-delimited ids received
     30    if (images.size() != 1)
     31    {
     32        LOG(VB_FILE, LOG_NOTICE,
     33            QString("Image: Image %1 not found in Db").arg(id));
     34        qDeleteAll(images);
     35        return QStringList("ERROR") << "Unknown File";
     36    }
     37
     38    // Get filepath for solitary image
     39    ImageItem *im = images[0];
     40    QString absFilename = ImageSg::getInstance()->GetFilePath(im);
     41
     42    if (absFilename.isEmpty())
     43    {
     44        delete im;
     45        return QStringList("ERROR") << "File not found";
     46    }
     47
     48    // Rename file
     49    QFileInfo info = QFileInfo(absFilename);
     50    QDir dir = info.absoluteDir();
     51    QString newName = im->IsDirectory()
     52            ? newBase : QString("%1.%2").arg(newBase, info.suffix());
     53
     54    if (!dir.rename(im->m_name, newName))
     55    {
     56        LOG(VB_FILE, LOG_ERR, QString("Image: Rename of %1 -> %2 failed")
     57            .arg(im->m_name, newName));
     58        delete im;
     59        return QStringList("ERROR") << "Rename failed";
     60    }
     61
     62    LOG(VB_FILE, LOG_DEBUG, QString("Image: Renamed %1 -> %2")
     63        .arg(im->m_fileName, newName));
     64
     65    ImageList dummy;
     66
     67    if (im->IsDirectory())
     68    {
     69        // Cleanup thumbdir/thumbnails
     70        // Thumb generator now owns the image object
     71        QStringList mesg = ImageThumb::getInstance()->DeleteThumbs(dummy, images);
     72
     73        // Notify clients of deleted ids, images, thumbs
     74        gCoreContext->SendEvent(MythEvent("IMAGE_DB_CHANGED", mesg));
     75
     76        // Dir name change affects path of all sub-dirs & files and their thumbs
     77        QStringList scan;
     78        scan << "IMAGE_SCAN" << "START";
     79        return ImageScan::getInstance()->HandleScanRequest(scan);
     80    }
     81
     82    // Retain old image for cleanup
     83    ImageItem *newIm = new ImageItem(*im);
     84
     85    // Update db
     86    newIm->m_name = newName;
     87    newIm->m_fileName = QDir::cleanPath(QDir(newIm->m_path).filePath(newName));
     88    db.UpdateDbFile(newIm);
     89    delete newIm;
     90
     91    // Clean up thumbnail
     92    // Thumb generator now owns the images objects
     93    QStringList mesg = ImageThumb::getInstance()->DeleteThumbs(images, dummy);
     94    // Item is modified, not deleted
     95    mesg.swap(0,1);
     96
     97    // New thumbnail will be created by client request
     98
     99    // Notify clients of changed image & thumbnail
     100    gCoreContext->SendEvent(MythEvent("IMAGE_DB_CHANGED", mesg));
     101
     102    return QStringList("OK");
     103}
     104
     105
     106/*!
     107 \brief Deletes images/dirs
     108 \details Removes images/dirs from Photographs storage group and image database.
     109 Dirs will only be deleted if empty. Synchronises thumbnail cache and broadcasts
     110 a 'db changed' event. Only fails if nothing is deleted.
     111 \param fileIds Csv list of dir/file ids
     112 \return QStringList Error message or "OK"
     113*/
     114QStringList ImageHandler::HandleDelete(QString fileIds)
     115{
     116    // Get subtree of files
     117    ImageDbWriter db;
     118    ImageList images, dirs;
     119    db.ReadDbTree(images, dirs, fileIds.split(","));
     120
     121    // Remove files from filesystem first
     122    ImageSg::getInstance()->RemoveFiles(images);
     123    // ... then dirs, which should now be empty
     124    ImageSg::getInstance()->RemoveFiles(dirs);
     125
     126    // Fail if nothing deleted
     127    if (images.isEmpty() && dirs.isEmpty())
     128        return QStringList("ERROR") << "Delete failed";
     129
     130    // Update Db
     131    db.RemoveFromDB(images + dirs);
     132
     133    // Clean up thumbnails & update clients
     134    // Thumb generator now owns the image objects
     135    QStringList mesg = ImageThumb::getInstance()->DeleteThumbs(images, dirs);
     136
     137    // Notify clients of deleted ids, images, thumbs
     138    gCoreContext->SendEvent(MythEvent("IMAGE_DB_CHANGED", mesg));
     139
     140    return QStringList("OK");
     141}
     142
     143
     144/*!
     145 \brief Gets meta data for an image
     146 \details Reads exif tags from a picture or FFMPEG video tags
     147 \param id Image id
     148 \return QStringList Error message or "OK", seperator,
     149list of <tag name><seperator><tag value>.
     150Clients must use the embedded seperator to extract the data.
     151*/
     152QStringList ImageHandler::HandleGetMetadata(QString id)
     153{
     154    // Find image in DB
     155    ImageDbWriter db;
     156    ImageList images;
     157    db.ReadDbFilesById(images, id);
     158
     159    // Either single id not found or multiple comma-delimited ids received
     160    if (images.size() != 1)
     161    {
     162        LOG(VB_FILE, LOG_NOTICE,
     163            QString("Image: Image %1 not found in Db").arg(id));
     164        qDeleteAll(images);
     165        return QStringList("ERROR") << "Unknown File";
     166    }
     167
     168    // Read all metadata tags
     169    ImageMetaData::TagMap tags;
     170    QStringList result;
     171
     172    if (ImageMetaData::GetMetaData(images[0], tags))
     173    {
     174        // Each property is described by a pair of <tagvalue> : <taglabel>
     175        // Combine label/value using a (hopefully unique) delimiter
     176        // to return 1 string per property.
     177        const QString seperator = ":|-|:";
     178        result << "OK" << seperator;
     179
     180        foreach (const ImageMetaData::TagPair value, tags)
     181        {
     182            result.append(QString("%1%2%3")
     183                          .arg(value.second).arg(seperator).arg(value.first));
     184        }
     185    }
     186    else
     187    {
     188        result = QStringList("ERROR") << QString("No metadata");
     189    }
     190    qDeleteAll(images);
     191
     192    return result;
     193}
     194
     195
     196/*!
     197 \brief Moves image trees
     198 \details Moves a list of images/dirs to an existing image dir. Renames files in
     199 the Photographs storage group and rescans to synchronise db and thumbnail cache
     200 and update clients. Only fails if nothing is moved.
     201 \param destId destination dir id
     202 \param ids Csv list of dir/file ids
     203 \return QStringList Error message or "OK"
     204*/
     205QStringList ImageHandler::HandleMove(QString destId, QString ids)
     206{
     207    // Validate destination
     208    ImageList destDir;
     209    ImageDbWriter db;
     210    db.ReadDbDirsById(destDir, destId);
     211
     212    // Either single id not found or multiple comma-delimited ids received
     213    if (destDir.size() != 1)
     214    {
     215        LOG(VB_FILE, LOG_ERR,
     216            QString("IMAGE_MOVE: Dir %1 not found in Db").arg(destId));
     217        qDeleteAll(destDir);
     218        return QStringList("ERROR") << "Missing destination";
     219    }
     220
     221    // Get transferees
     222    ImageList images;
     223    db.ReadDbItemsById(images, ids);
     224
     225    bool changed = ImageSg::getInstance()->MoveFiles(images, destDir[0]);
     226
     227    delete destDir[0];
     228    qDeleteAll(images);
     229
     230    if (!changed)
     231        return QStringList("ERROR") << "Move failed";
     232
     233    // Rescan to update Db & clients
     234    QStringList scan;
     235    scan << "IMAGE_SCAN" << "START";
     236    return ImageScan::getInstance()->HandleScanRequest(scan);
     237}
     238
     239
     240/*!
     241 \brief Hides/unhides images/dirs
     242 \details Updates hidden status in image database and updates clients
     243 \param hide hide flag: 0 = Show, 1 = Hide
     244 \param fids Csv list of file/dir ids
     245 \return QStringList Error message or "OK"
     246*/
     247QStringList ImageHandler::HandleHide(bool hide, QString fids)
     248{
     249    // Extract ids
     250    QStringList fileIds = fids.split(",", QString::SkipEmptyParts);
     251
     252    ImageDbWriter db;
     253    if (!db.SetHidden(hide, fileIds))
     254        return QStringList("ERROR") << "Hide failed";
     255
     256    LOG(VB_FILE, LOG_DEBUG, QString("Image: Nodes %1 now %2hidden")
     257        .arg(fids, hide ? "" : "un"));
     258
     259    // Send changed ids only (none deleted)
     260    QStringList mesg = QStringList("") << fids;
     261    gCoreContext->SendEvent(MythEvent("IMAGE_DB_CHANGED", mesg));
     262
     263    return QStringList("OK");
     264}
     265
     266/*!
     267 \brief Change orientation of pictures by applying a transformation
     268 \details Updates picture exif and image database, removes obselete thumbnails and
     269 updates clients. New thumbnails will be generated by client request. Only fails if
     270 nothing is modified.
     271 \param transform transformation id,
     272 \param fileIds Csv list of file ids
     273 \return QStringList Error message or "OK"
     274*/
     275QStringList ImageHandler::HandleTransform(int transform, QString fileIds)
     276{
     277    if (transform < kResetExif || transform > kFlipVertical)
     278        return QStringList("ERROR") << "Bad IMAGE_TRANSFORM transform";
     279
     280    if (fileIds.isEmpty())
     281        return QStringList("ERROR") << "Empty IMAGE_TRANSFORM";
     282
     283    ImageDbWriter db;
     284    ImageList images;
     285    db.ReadDbFilesById(images, fileIds);
     286
     287    // Update db
     288    foreach (ImageItem *im, images)
     289    {
     290        if (transform == kResetExif)
     291        {
     292            ImageMetaData::PopulateMetaValues(im);
     293        }
     294        else
     295        {
     296            // Apply transform to this image
     297            im->m_orientation = ExifOrientation::Transformed(im->m_orientation,
     298                                                             transform);
     299        }
     300        db.SetOrientation(im);
     301    }
     302
     303    // Clean up thumbnails & update clients
     304    // Thumb generator now owns the image objects
     305    ImageList noDirs;
     306    QStringList mesg = ImageThumb::getInstance()->DeleteThumbs(images, noDirs);
     307
     308    // Swap ids from 'deleted' to 'changed'
     309    mesg.swap(0,1);
     310    // Notify clients of changed images, thumbs
     311    gCoreContext->SendEvent(MythEvent("IMAGE_DB_CHANGED", mesg));
     312
     313    return QStringList("OK");
     314}
     315
     316/*!
     317 \brief Creates a new image dirs
     318 \details Creates dirs in Photographs storage group 'most free' path. Only fails if
     319 no new dirs are created. Other clients are not notified.
     320 \param names Csv list of dir names
     321 \return QStringList Error message or "OK"
     322*/
     323QStringList ImageHandler::HandleDirs(QStringList names)
     324{
     325    // Note: Could/should be implemented by RemoteFile
     326
     327    // Get SG dir
     328    QString sgDir = ImageSg::getInstance()->m_sgImages.FindNextDirMostFree();
     329    if (sgDir.isEmpty())
     330        return QStringList("ERROR") << "Empty Storage Group";
     331
     332    bool success = false;
     333    QDir dir;
     334    for (int i = 0; i < names.size(); ++i)
     335    {
     336        if (dir.mkpath(QString("%1/%2").arg(sgDir, names[i])))
     337            success = true;
     338        else
     339            LOG(VB_FILE, LOG_ERR, QString("Image: Failed to create dir %1")
     340                .arg(names[i]));
     341    }
     342
     343    return success ? QStringList("OK")
     344                   : QStringList("ERROR") << "Create failed";
     345}
     346
     347/*!
     348 \brief Updates/resets cover thumbnail for an image dir
     349 \details Notifies all clients of new cover
     350 \param dir Directory id
     351 \param Cover Id of 0 resets dir to use its own thumbnail
     352 \return QStringList Error message or "OK"
     353*/
     354QStringList ImageHandler::HandleCover(int dir, int cover)
     355{
     356    ImageDbWriter db;
     357    db.SetCover(dir, cover);
     358
     359    LOG(VB_FILE, LOG_DEBUG, QString("Image: Cover of %1 is now %2")
     360        .arg(dir).arg(cover));
     361
     362    // Id has changed, nothing deleted
     363    QStringList mesg = QStringList("") << QString::number(dir);
     364    gCoreContext->SendEvent(MythEvent("IMAGE_DB_CHANGED", mesg));
     365
     366    return QStringList("OK");
     367}
     368
     369/*!
     370 \brief Updates exclusion list for images
     371 \details Stores new exclusions setting & rescans. Exclusions is a global setting
     372 that dictates which files the scanner ignores. However it is set by any client
     373 (last writer wins). Glob characters * and ? are valid.
     374 \param exclusions Csv list of exclusion patterns
     375 \return QStringList Error message or "OK"
     376*/
     377QStringList ImageHandler::HandleIgnore(QString exclusions)
     378{
     379    // Save new setting. FE will have already saved it but not cleared the cache
     380    gCoreContext->SaveSettingOnHost("GalleryIgnoreFilter", exclusions, NULL);
     381
     382    // Rescan
     383    QStringList scan;
     384    scan << "IMAGE_SCAN" << "START";
     385    ImageScan *is = ImageScan::getInstance();
     386    return is->HandleScanRequest(scan);
     387}
     388
  • new file mythtv/programs/mythbackend/imagehandlers.h

    diff --git a/mythtv/programs/mythbackend/imagehandlers.h b/mythtv/programs/mythbackend/imagehandlers.h
    new file mode 100644
    index 0000000..ffa7ac6
    - +  
     1//! \file
     2//! \brief Encapsulates BE
     3
     4#ifndef IMAGEHANDLERS_H
     5#define IMAGEHANDLERS_H
     6
     7#include <QStringList>
     8
     9//! Processes BE requests regarding images
     10class ImageHandler
     11{
     12public:
     13
     14    static QStringList HandleRename(QString, QString);
     15    static QStringList HandleDelete(QString);
     16    static QStringList HandleGetMetadata(QString);
     17    static QStringList HandleMove(QString, QString);
     18    static QStringList HandleHide(bool, QString);
     19    static QStringList HandleTransform(int, QString);
     20    static QStringList HandleDirs(QStringList);
     21    static QStringList HandleCover(int, int);
     22    static QStringList HandleIgnore(QString);
     23};
     24
     25#endif // IMAGEHANDLERS_H
  • mythtv/programs/mythbackend/mainserver.cpp

    diff --git a/mythtv/programs/mythbackend/mainserver.cpp b/mythtv/programs/mythbackend/mainserver.cpp
    index 8ce40e8..3ef2646 100644
    a b using namespace std; 
    7474#include "filesysteminfo.h"
    7575#include "metaio.h"
    7676#include "musicmetadata.h"
    77 #include "imagescan.h"
    78 #include "imagethumbgenthread.h"
    79 #include "imageutils.h"
     77#include "imagescanner.h"
     78#include "imagethumbs.h"
     79#include "imagehandlers.h"
    8080
    8181// mythbackend headers
    8282#include "backendcontext.h"
    void MainServer::ProcessRequestWork(MythSocket *sock) 
    897897    }
    898898    else if (command == "IMAGE_SCAN")
    899899    {
    900         QString err = QString("Bad IMAGE_SCAN");
    901 
    902         if (listline.size() == 2)
    903 
    904             err = HandleScanImages(listline, pbs);
    905 
    906         if (err.isEmpty())
    907         {
    908             QStringList ok = QStringList("OK");
    909             SendResponse(pbs->getSocket(), ok);
    910         }
    911         else
    912             SendErrorResponse(pbs, err);
     900        QStringList reply = ImageScan::getInstance()->HandleScanRequest(listline);
     901        SendResponse(pbs->getSocket(), reply);
    913902    }
    914     else if (command == "IMAGE_GET_SCAN_STATUS")
     903    else if (command == "IMAGE_MOVE")
    915904    {
    916         if (listline.size() != 1)
    917             SendErrorResponse(pbs, "Bad IMAGE_GET_SCAN_STATUS");
    918         else
    919             HandleQueryImageScanStatus(pbs);
    920     }
    921     else if (command == "IMAGE_THUMBNAILS")
    922     {
    923         if (listline.size() < 3)
    924             SendErrorResponse(pbs, "Bad IMAGE_THUMBNAILS");
    925         else
    926             HandleCreateThumbnails(listline, pbs);
     905        // Expects destination dir id, comma-delimited dir/file ids
     906        QStringList reply = (listline.size() == 3)
     907                         ? ImageHandler::HandleMove(listline[1], listline[2])
     908                         : QStringList("ERROR") << "Bad IMAGE_MOVE";
     909
     910        SendResponse(pbs->getSocket(), reply);
    927911    }
    928912    else if (command == "IMAGE_DELETE")
    929913    {
    930         QString err = QString("Bad IMAGE_DELETE");
     914        // Expects comma-delimited dir/file ids
     915        QStringList reply = (listline.size() == 2)
     916                ? ImageHandler::HandleDelete(listline[1])
     917                : QStringList("ERROR") << "Bad IMAGE_DELETE";
    931918
    932         if (listline.size() == 2)
     919        SendResponse(pbs->getSocket(), reply);
     920    }
     921    else if (command == "IMAGE_HIDE")
     922    {
     923        // Expects hide flag, comma-delimited file/dir ids
     924        QStringList reply = (listline.size() == 3)
     925                ? ImageHandler::HandleHide(listline[1].toInt(), listline[2])
     926                : QStringList("ERROR") << "Bad IMAGE_HIDE";
    933927
    934             err = HandleDeleteImage(listline, pbs);
     928        SendResponse(pbs->getSocket(), reply);
     929    }
     930    else if (command == "IMAGE_TRANSFORM")
     931    {
     932        // Expects transformation, write file flag,
     933        QStringList reply = (listline.size() == 3)
     934                ? ImageHandler::HandleTransform(listline[1].toInt(), listline[2])
     935                : QStringList("ERROR") << "Bad IMAGE_TRANSFORM";
    935936
    936         if (err.isEmpty())
    937         {
    938             QStringList ok = QStringList("OK");
    939             SendResponse(pbs->getSocket(), ok);
    940         }
    941         else
    942             SendErrorResponse(pbs, err);
     937        SendResponse(pbs->getSocket(), reply);
    943938    }
    944939    else if (command == "IMAGE_RENAME")
    945940    {
    946         QString err = QString("Bad IMAGE_RENAME");
    947 
    948         if (listline.size() == 3)
     941        // Expects file/dir id, new basename
     942        QStringList reply = (listline.size() == 3)
     943                ? ImageHandler::HandleRename(listline[1], listline[2])
     944                : QStringList("ERROR") << "Bad IMAGE_RENAME";
    949945
    950             err = HandleRenameImage(listline, pbs);
    951 
    952         if (err.isEmpty())
    953         {
    954             QStringList ok = QStringList("OK");
    955             SendResponse(pbs->getSocket(), ok);
    956         }
    957         else
    958             SendErrorResponse(pbs, err);
     946        SendResponse(pbs->getSocket(), reply);
    959947    }
    960     else if (command == "IMAGE_SET_EXIF")
     948    else if (command == "IMAGE_CREATE_DIRS")
    961949    {
    962         QString err = QString("Bad IMAGE_SET_EXIF");
    963 
    964         if (listline.size() == 4)
     950        // Expects list of dir names
     951        QStringList reply = (listline.size() == 2)
     952                ? ImageHandler::HandleDirs(listline[1].split(","))
     953                : QStringList("ERROR") << "Bad IMAGE_CREATE_DIRS";
    965954
    966             err = HandleSetImageExif(listline, pbs);
    967 
    968         if (err.isEmpty())
    969         {
    970             QStringList ok = QStringList("OK");
    971             SendResponse(pbs->getSocket(), ok);
    972         }
    973         else
    974             SendErrorResponse(pbs, err);
     955        SendResponse(pbs->getSocket(), reply);
    975956    }
    976     else if (command == "IMAGE_GET_EXIF")
     957    else if (command == "IMAGE_COVER")
    977958    {
    978         QString err = QString("Bad IMAGE_GET_EXIF");
    979 
    980         if (listline.size() == 2)
     959        // Expects dir id, cover id. Cover id of 0 resets dir to use its own
     960        QStringList reply = (listline.size() == 3)
     961                ? ImageHandler::HandleCover(listline[1].toInt(), listline[2].toInt())
     962                : QStringList("ERROR") << "Bad IMAGE_COVER";
    981963
    982             err = HandleGetImageExif(listline, pbs);
     964        SendResponse(pbs->getSocket(), reply);
     965    }
     966    else if (command == "IMAGE_GET_METADATA")
     967    {
     968        // Expects "IMAGE_GET_METADATA", image id
     969        QStringList reply = (listline.size() == 2)
     970                ? ImageHandler::HandleGetMetadata(listline[1])
     971                : QStringList("ERROR") << "Bad IMAGE_GET_METADATA";
    983972
    984         if (!err.isEmpty())
     973        SendResponse(pbs->getSocket(), reply);
     974    }
     975    else if (command == "IMAGE_IGNORE")
     976    {
     977        // Expects list of exclusion patterns
     978        QStringList reply = (listline.size() == 2)
     979                ? ImageHandler::HandleIgnore(listline[1])
     980                : QStringList("ERROR") << "Bad IMAGE_IGNORE";
    985981
    986             SendErrorResponse(pbs, err);
     982        SendResponse(pbs->getSocket(), reply);
    987983    }
    988984    else if (command == "ALLOW_SHUTDOWN")
    989985    {
    void MainServer::customEvent(QEvent *e) 
    13791375        if (me->Message().startsWith("LOCAL_"))
    13801376            return;
    13811377
     1378        if (me->Message() == "CREATE_THUMBNAILS")
     1379            ImageThumb::getInstance()->HandleCreateThumbnails(me->ExtraDataList());
     1380
    13821381        MythEvent mod_me("");
    13831382        if (me->Message().startsWith("MASTER_UPDATE_REC_INFO"))
    13841383        {
    void MainServer::HandleMusicTagAddImage(const QStringList& slist, PlaybackSock* 
    64956494        SendResponse(pbssock, strlist);
    64966495}
    64976496
     6497
    64986498void MainServer::HandleMusicTagRemoveImage(const QStringList& slist, PlaybackSock* pbs)
    64996499{
    65006500// format: MUSIC_TAG_REMOVEIMAGE <hostname> <songid> <imageid>
    void MainServer::HandleMusicTagRemoveImage(const QStringList& slist, PlaybackSoc 
    66256625        SendResponse(pbssock, strlist);
    66266626}
    66276627
    6628 // Expects: START or STOP
    6629 // Replies: OK or error
    6630 QString MainServer::HandleScanImages(QStringList &slist,
    6631                                      PlaybackSock *pbs)
    6632 {
    6633     ImageScan *is = ImageScan::getInstance();
    6634 
    6635     if (slist[1] == "START")
    6636 
    6637         is->StartSync();
    6638 
    6639     else if (slist[1] == "STOP")
    6640 
    6641         is->StopSync();
    6642 
    6643     else
    6644         return QString("Unknown Command %1").arg(slist[1]);
    6645 
    6646     return QString();
    6647 }
    6648 
    6649 // Expects: nothing
    6650 // Replies: current scan status as an int (0 or 1)
    6651 //        : number of images already scanned
    6652 //        : total number of images detected
    6653 void MainServer::HandleQueryImageScanStatus(PlaybackSock *pbs)
    6654 {
    6655     ImageScan *is = ImageScan::getInstance();
    6656     QStringList strlist;
    6657 
    6658     strlist << "OK"
    6659             << QString::number(is->SyncIsRunning()) // return bool as an int
    6660             << QString::number(is->GetCurrent())
    6661             << QString::number(is->GetTotal());
    6662 
    6663     SendResponse(pbs->getSocket(), strlist);
    6664 }
    6665 
    6666 // Expects: recreate-even-if-already-present boolean flag, list of image ids
    6667 // Replies: OK
    6668 void MainServer::HandleCreateThumbnails(QStringList &slist,
    6669                                         PlaybackSock *pbs)
    6670 {
    6671     // single flag applies to all thumbnails
    6672     bool recreate = slist[1].toInt();
    6673 
    6674     ImageUtils *iu = ImageUtils::getInstance();
    6675     ImageThumbGen *thumbGen = ImageThumbGen::getInstance();
    6676 
    6677     for (int i = 2; i < slist.size(); ++i)
    6678     {
    6679         int id = slist[i].toInt();
    6680 
    6681         ImageMetadata *im = new ImageMetadata();
    6682 
    6683         // find requested image in DB
    6684         iu->LoadFileFromDB(im, id);
    6685 
    6686         if (im->m_fileName.isEmpty())
    6687         {
    6688             LOG(VB_FILE, LOG_DEBUG, QString("Image %1 not found in Db").arg(id));
    6689             delete im;
    6690         }
    6691         else
    6692             // pass to thumbnail generator
    6693             thumbGen->AddToThumbnailList(im, recreate);
    6694     }
    6695 
    6696     // Restart generator
    6697     thumbGen->StartThumbGen();
    6698 
    6699     QStringList ok = QStringList("OK");
    6700     SendResponse(pbs->getSocket(), ok);
    6701 }
    6702 
    6703 // Expects: image id
    6704 // Replies: error or nothing
    6705 QString MainServer::HandleDeleteImage(QStringList &slist, PlaybackSock *pbs)
    6706 {
    6707     QString sgName = IMAGE_STORAGE_GROUP;
    6708     StorageGroup sg = StorageGroup(sgName, gCoreContext->GetHostName());
    6709     ImageUtils *iu = ImageUtils::getInstance();
    6710 
    6711     int id = slist[1].toInt();
    6712 
    6713     // Find image in DB
    6714     ImageMetadata *im = new ImageMetadata();
    6715     iu->LoadFileFromDB(im, id);
    6716     QString fileName = im->m_fileName;
    6717     delete im;
    6718 
    6719     if (fileName.isEmpty())
    6720 
    6721         return QString("Image %1 not found in Db").arg(id);
    6722 
    6723     // Find image file
    6724     QString imageFileName = sg.FindFile(fileName);
    6725 
    6726     if (imageFileName.isEmpty())
    6727 
    6728         return QString("File %1 for image %2 not found")
    6729                 .arg(fileName)
    6730                 .arg(id);
    6731 
    6732     // Remove file
    6733     if (!QFile::remove(imageFileName))
    6734 
    6735         return QString("Could not delete file %1 for image %2")
    6736                 .arg(imageFileName)
    6737                 .arg(id);
    6738 
    6739     LOG(VB_FILE, LOG_DEBUG,
    6740         QString("Deleted %1 for image %2")
    6741         .arg(imageFileName)
    6742         .arg(id));
    6743 
    6744     // Remove the database entry once the file has been deleted.
    6745     if (!iu->RemoveFileFromDB(id))
    6746 
    6747         return QString("Delete from Db failed for image %1").arg(id);
    6748 
    6749     // success
    6750     return QString();
    6751 }
    6752 
    6753 // Expects: image id, new name
    6754 // Replies: error or nothing
    6755 QString MainServer::HandleRenameImage(QStringList &slist, PlaybackSock *pbs)
    6756 {
    6757     QString sgName = IMAGE_STORAGE_GROUP;
    6758     StorageGroup sg = StorageGroup(sgName, gCoreContext->GetHostName(), false);
    6759     ImageUtils *iu = ImageUtils::getInstance();
    6760 
    6761     int id = slist[1].toInt();
    6762     QString newName = slist[2];
    6763 
    6764     // New name must not contain a path
    6765     if (newName.contains("/") || newName.contains("\\"))
    6766 
    6767         return QString("New filename '%1' for image %2 must "
    6768                        "not contain a path")
    6769                 .arg(newName)
    6770                 .arg(id);
    6771 
    6772     // Find image in DB
    6773     ImageMetadata *im = new ImageMetadata();
    6774     iu->LoadFileFromDB(im, id);
    6775     QString relFileName = im->m_fileName;
    6776     QString name = im->m_name;
    6777     QString path = im->m_path;
    6778     delete im;
    6779 
    6780     if (relFileName.isEmpty())
    6781 
    6782         return QString("Image %1 not found in Db").arg(id);
    6783 
    6784     // Find image file
    6785     QString absFileName = sg.FindFile(relFileName);
    6786 
    6787     if (absFileName.isEmpty())
    6788 
    6789         return QString("File %1 for image %2 not found")
    6790                 .arg(relFileName)
    6791                 .arg(id);
    6792 
    6793     // Rename the file
    6794     QFile file;
    6795     file.setFileName(absFileName);
    6796     QFileInfo info = QFileInfo(file);
    6797     QString absNewName = QString("%1/%2").arg(info.absolutePath()).arg(newName);
    6798 
    6799     if (!file.rename(absNewName))
    6800 
    6801         return QString("Renaming %1 to %2 failed for image %3")
    6802                 .arg(name)
    6803                 .arg(newName)
    6804                 .arg(id);
    6805 
    6806     LOG(VB_FILE, LOG_DEBUG,
    6807         QString("Renamed %1 to %2 for image %3")
    6808         .arg(name)
    6809         .arg(newName)
    6810         .arg(id));
    6811 
    6812     // Update the database
    6813     MSqlQuery query(MSqlQuery::InitCon());
    6814     query.prepare(QString("UPDATE gallery_files SET "
    6815                           "name           = :NAME "
    6816                           "WHERE file_id  = :ID;"));
    6817     query.bindValue(":NAME", newName);
    6818     query.bindValue(":ID",   id);
    6819 
    6820     if (!query.exec())
    6821 
    6822         return QString("Rename in Db failed for image %1").arg(id);
    6823 
    6824     // success
    6825     return QString();
    6826 }
    6827 
    6828 // Expects: image id, property, tag value
    6829 // Replies: error or nothing
    6830 QString MainServer::HandleSetImageExif(QStringList &slist, PlaybackSock *pbs)
    6831 {
    6832     QString sgName = IMAGE_STORAGE_GROUP;
    6833     StorageGroup sg = StorageGroup(sgName, gCoreContext->GetHostName());
    6834     ImageUtils *iu = ImageUtils::getInstance();
    6835 
    6836     int id = slist[1].toInt();
    6837     QString property = slist[2];
    6838     QString tagValue = slist[3];
    6839 
    6840     // validate
    6841     QString tag;
    6842     int value;
    6843     if (property == "ORIENTATION")
    6844     {
    6845         tag = "Exif.Image.Orientation";
    6846         value = tagValue.toInt();
    6847 
    6848         // See http://jpegclub.org/exif_orientation.html for details
    6849         if ( value < 1 || value > 8)
    6850 
    6851             return QString("Invalid orientation %1 requested for image %2")
    6852                     .arg(tagValue)
    6853                     .arg(id);
    6854     }
    6855     else
    6856         return QString("Setting property %1 not implemented").arg(property);
    6857 
    6858     // Find image in DB
    6859     ImageMetadata *im = new ImageMetadata();
    6860     iu->LoadFileFromDB(im, id);
    6861     QString fileName = im->m_fileName;
    6862     delete im;
    6863 
    6864     if (fileName.isEmpty())
    6865 
    6866         return QString("Image %1 not found in Db").arg(id);
    6867 
    6868     // Find image file
    6869     QString imageFileName = sg.FindFile(fileName);
    6870 
    6871     if (imageFileName.isEmpty())
    6872 
    6873         return QString("File %1 for image %2 not found")
    6874                 .arg(fileName)
    6875                 .arg(id);
    6876 
    6877     // Set Exif
    6878     bool ok;
    6879     iu->SetExifValue(imageFileName, tag, tagValue, &ok);
    6880 
    6881     if (!ok)
    6882 
    6883         return QString("Setting exif %1 failed for image %2")
    6884                 .arg(property)
    6885