diff --git a/dive.c b/dive.c index 758368615..fc0fbb855 100644 --- a/dive.c +++ b/dive.c @@ -392,6 +392,7 @@ static void copy_pl(struct picture *sp, struct picture *dp) { *dp = *sp; dp->filename = copy_string(sp->filename); + dp->hash = copy_string(sp->hash); } /* copy an element in a list of tags */ @@ -2936,6 +2937,7 @@ static void picture_free(struct picture *p) if (!p) return; free(p->filename); + free(p->hash); free(p); } void dive_remove_picture(char *filename) diff --git a/dive.h b/dive.h index 91c160e1f..e189b860d 100644 --- a/dive.h +++ b/dive.h @@ -366,6 +366,7 @@ struct dive_components { /* picture list and methods related to dive picture handling */ struct picture { char *filename; + char *hash; offset_t offset; degrees_t latitude; degrees_t longitude; diff --git a/load-git.c b/load-git.c index 053a7140e..be25e8e94 100644 --- a/load-git.c +++ b/load-git.c @@ -828,6 +828,12 @@ static void parse_picture_gps(char *line, struct membuffer *str, void *_pic) pic->longitude = parse_degrees(line, &line); } +static void parse_picture_hash(char *line, struct membuffer *str, void *_pic) +{ + struct picture *pic = _pic; + pic->hash = get_utf8(str); +} + /* These need to be sorted! */ struct keyword_action dc_action[] = { #undef D @@ -900,7 +906,7 @@ static void settings_parser(char *line, struct membuffer *str, void *_unused) static struct keyword_action picture_action[] = { #undef D #define D(x) { #x, parse_picture_ ## x } - D(filename), D(gps) + D(filename), D(gps), D(hash) }; static void picture_parser(char *line, struct membuffer *str, void *_pic) diff --git a/parse-xml.c b/parse-xml.c index 90559d6c4..a42f8c854 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -1296,6 +1296,8 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) return; if (MATCH("gps.picture", gps_picture_location, cur_picture)) return; + if (MATCH("hash.picture", utf8_string, &cur_picture->hash)) + return; if (MATCH("cylinderstartpressure", pressure, &dive->cylinder[0].start)) return; if (MATCH("cylinderendpressure", pressure, &dive->cylinder[0].end)) diff --git a/qt-ui/divepicturewidget.cpp b/qt-ui/divepicturewidget.cpp index 92695b6a6..a0d209b6d 100644 --- a/qt-ui/divepicturewidget.cpp +++ b/qt-ui/divepicturewidget.cpp @@ -3,7 +3,25 @@ #include "dive.h" #include "divelist.h" #include +#include #include +#include +#include +#include + +SHashedImage::SHashedImage(struct picture *picture) : QImage(picture->filename) +{ + if (isNull()) { + // Hash lookup. + load(fileFromHash(picture->hash)); + if (!isNull()) + QtConcurrent::run(updateHash, picture); + } else { + QByteArray hash = hashFile(QString(picture->filename)); + free(picture->hash); + picture->hash = strdup(hash.toHex().data()); + } +} DivePictureModel *DivePictureModel::instance() { @@ -15,20 +33,21 @@ DivePictureModel::DivePictureModel() : numberOfPictures(0) { } -typedef QPair SPixmap; -typedef QList SPixmapList; +typedef struct picture *picturepointer; +typedef QPair SPixmap; +typedef QList SPictureList; -SPixmap scaleImages(const QString &s) +SPixmap scaleImages(picturepointer picture) { static QHash cache; SPixmap ret; - ret.first = s; - if (cache.contains(s)) { - ret.second = cache.value(s); + ret.first = picture; + if (cache.contains(picture->filename) && !cache.value(picture->filename).isNull()) { + ret.second = cache.value(picture->filename); } else { int dim = defaultIconMetrics().sz_pic; - QImage p = QImage(s).scaled(dim, dim, Qt::KeepAspectRatio); - cache.insert(s, p); + QImage p = SHashedImage(picture).scaled(dim, dim, Qt::KeepAspectRatio); + cache.insert(picture->filename, p); ret.second = p; } return ret; @@ -49,14 +68,15 @@ void DivePictureModel::updateDivePictures() } stringPixmapCache.clear(); - QStringList pictures; + SPictureList pictures; FOR_EACH_PICTURE_NON_PTR(displayed_dive) { stringPixmapCache[QString(picture->filename)].offsetSeconds = picture->offset.seconds; - pictures.push_back(QString(picture->filename)); + pictures.push_back(picture); } - Q_FOREACH (const SPixmap &pixmap, QtConcurrent::blockingMapped(pictures, scaleImages)) - stringPixmapCache[pixmap.first].image = pixmap.second; + QList list = QtConcurrent::blockingMapped(pictures, scaleImages); + Q_FOREACH (const SPixmap &pixmap, list) + stringPixmapCache[pixmap.first->filename].image = pixmap.second; beginInsertRows(QModelIndex(), 0, numberOfPictures - 1); endInsertRows(); @@ -121,5 +141,5 @@ DivePictureWidget::DivePictureWidget(QWidget *parent) : QListView(parent) void DivePictureWidget::doubleClicked(const QModelIndex &index) { QString filePath = model()->data(index, Qt::DisplayPropertyRole).toString(); - emit photoDoubleClicked(filePath); + emit photoDoubleClicked(localFilePath(filePath)); } diff --git a/qt-ui/divepicturewidget.h b/qt-ui/divepicturewidget.h index aa524e1a6..e8104a1db 100644 --- a/qt-ui/divepicturewidget.h +++ b/qt-ui/divepicturewidget.h @@ -5,11 +5,18 @@ #include #include +typedef QPair SHashedFilename; + struct PhotoHelper { QImage image; int offsetSeconds; }; +class SHashedImage : public QImage { +public: + SHashedImage(struct picture *picture); +}; + class DivePictureModel : public QAbstractTableModel { Q_OBJECT public: diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 7b204626c..8c3b44c48 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -37,6 +37,8 @@ #endif #include #include +#include +#include MainWindow *MainWindow::m_Instance = NULL; @@ -50,6 +52,7 @@ MainWindow::MainWindow() : QMainWindow(), Q_ASSERT_X(m_Instance == NULL, "MainWindow", "MainWindow recreated!"); m_Instance = this; ui.setupUi(this); + read_hashes(); // Define the States of the Application Here, Currently the states are situations where the different // widgets will change on the mainwindow. @@ -201,6 +204,7 @@ MainWindow::MainWindow() : QMainWindow(), MainWindow::~MainWindow() { + write_hashes(); m_Instance = NULL; } diff --git a/qthelper.cpp b/qthelper.cpp index 4cedc5597..74c67d769 100644 --- a/qthelper.cpp +++ b/qthelper.cpp @@ -27,6 +27,11 @@ #include #include #include +#include +#include +#include +#include +#include "divepicturewidget.h" #include @@ -780,3 +785,67 @@ extern "C" void reverseGeoLookup(degrees_t latitude, degrees_t longitude, uint32 ds->notes = add_to_string(ds->notes, "countrytag: %s", address.value("country").toString().toUtf8().data()); } } + +QHash hashOf; +QHash localFilenameOf; + +extern "C" char * hashstring(char * filename) +{ + return hashOf[QString(filename)].toHex().data(); +} + +void read_hashes() +{ + QFile hashfile(QString(system_default_directory()).append("/hashes")); + if (hashfile.open(QIODevice::ReadOnly)) { + QDataStream stream(&hashfile); + stream >> localFilenameOf; + hashfile.close(); + } +} + +void write_hashes() +{ + QSaveFile hashfile(QString(system_default_directory()).append("/hashes")); + if (hashfile.open(QIODevice::WriteOnly)) { + QDataStream stream(&hashfile); + stream << localFilenameOf; + hashfile.commit(); + } else { + qDebug() << "cannot open" << hashfile.fileName(); + } +} + +void add_hash(const QString filename, QByteArray hash) +{ + hashOf[filename] = hash; + localFilenameOf[hash] = filename; +} + +QByteArray hashFile(const QString filename) +{ + QCryptographicHash hash(QCryptographicHash::Sha1); + QFile imagefile(filename); + imagefile.open(QIODevice::ReadOnly); + hash.addData(&imagefile); + add_hash(filename, hash.result()); + return hash.result(); +} + +QString localFilePath(const QString originalFilename) +{ + return localFilenameOf[hashOf[originalFilename]]; +} + +QString fileFromHash(char *hash) +{ + return localFilenameOf[QByteArray::fromHex(hash)]; +} + +void updateHash(struct picture *picture) { + QByteArray hash = hashFile(fileFromHash(picture->hash)); + hashOf[QString(picture->filename)] = hash; + char *old = picture->hash; + picture->hash = strdup(hash.toHex()); + free(old); +} diff --git a/qthelper.h b/qthelper.h index a367a9dac..e164a41b6 100644 --- a/qthelper.h +++ b/qthelper.h @@ -7,6 +7,7 @@ #include "dive.h" #include "divelist.h" #include +#include // global pointers for our translation extern QTranslator *qtTranslator, *ssrfTranslator; @@ -16,5 +17,11 @@ bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_te extern "C" const char *printGPSCoords(int lat, int lon); QList getDivesInTrip(dive_trip_t *trip); QString gasToStr(struct gasmix gas); - +void read_hashes(); +void write_hashes(); +void updateHash(struct picture *picture); +QByteArray hashFile(const QString filename); +void add_hash(const QString filename, QByteArray &hash); +QString localFilePath(const QString originalFilename); +QString fileFromHash(char *hash); #endif // QTHELPER_H diff --git a/save-git.c b/save-git.c index 0125f64d7..a7b51446a 100644 --- a/save-git.c +++ b/save-git.c @@ -585,6 +585,7 @@ static int save_one_picture(git_repository *repo, struct dir *dir, struct pictur show_utf8(&buf, "filename ", pic->filename, "\n"); show_gps(&buf, pic->latitude, pic->longitude); + show_utf8(&buf, "hash ", pic->hash, "\n"); /* Picture loading will load even negative offsets.. */ if (offset < 0) { diff --git a/save-xml.c b/save-xml.c index 8366f87ff..c68b5b633 100644 --- a/save-xml.c +++ b/save-xml.c @@ -359,6 +359,8 @@ static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer put_format(b, " \n"); } +extern char * hashstring(char * filename); + static void save_picture(struct membuffer *b, struct picture *pic) { put_string(b, " longitude, "", "'"); } + if (hashstring(pic->filename)) + put_format(b, " hash='%s'", hashstring(pic->filename)); + put_string(b, "/>\n"); }