Add hashes to images

Upon successfull reading an image file, this computes a SHA1 hash of the
image and saves it with the picture tag in the log file. When a file is
not successfully loaded (for example because the log was created on a
different computer) we look up the hash in a dictionary that maps hashes
to local file names.

That dictionary (actually two for both directions), is loaded on startup
and saved upon destruction of the main window.

Signed-off-by: Robert C. Helling <helling@atdotde.de>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Robert C. Helling 2015-02-26 14:39:42 +01:00 committed by Dirk Hohndel
parent ea00fdb36a
commit b02bf002a6
11 changed files with 139 additions and 15 deletions

2
dive.c
View file

@ -392,6 +392,7 @@ static void copy_pl(struct picture *sp, struct picture *dp)
{ {
*dp = *sp; *dp = *sp;
dp->filename = copy_string(sp->filename); dp->filename = copy_string(sp->filename);
dp->hash = copy_string(sp->hash);
} }
/* copy an element in a list of tags */ /* copy an element in a list of tags */
@ -2936,6 +2937,7 @@ static void picture_free(struct picture *p)
if (!p) if (!p)
return; return;
free(p->filename); free(p->filename);
free(p->hash);
free(p); free(p);
} }
void dive_remove_picture(char *filename) void dive_remove_picture(char *filename)

1
dive.h
View file

@ -366,6 +366,7 @@ struct dive_components {
/* picture list and methods related to dive picture handling */ /* picture list and methods related to dive picture handling */
struct picture { struct picture {
char *filename; char *filename;
char *hash;
offset_t offset; offset_t offset;
degrees_t latitude; degrees_t latitude;
degrees_t longitude; degrees_t longitude;

View file

@ -828,6 +828,12 @@ static void parse_picture_gps(char *line, struct membuffer *str, void *_pic)
pic->longitude = parse_degrees(line, &line); 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! */ /* These need to be sorted! */
struct keyword_action dc_action[] = { struct keyword_action dc_action[] = {
#undef D #undef D
@ -900,7 +906,7 @@ static void settings_parser(char *line, struct membuffer *str, void *_unused)
static struct keyword_action picture_action[] = { static struct keyword_action picture_action[] = {
#undef D #undef D
#define D(x) { #x, parse_picture_ ## x } #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) static void picture_parser(char *line, struct membuffer *str, void *_pic)

View file

@ -1296,6 +1296,8 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf)
return; return;
if (MATCH("gps.picture", gps_picture_location, cur_picture)) if (MATCH("gps.picture", gps_picture_location, cur_picture))
return; return;
if (MATCH("hash.picture", utf8_string, &cur_picture->hash))
return;
if (MATCH("cylinderstartpressure", pressure, &dive->cylinder[0].start)) if (MATCH("cylinderstartpressure", pressure, &dive->cylinder[0].start))
return; return;
if (MATCH("cylinderendpressure", pressure, &dive->cylinder[0].end)) if (MATCH("cylinderendpressure", pressure, &dive->cylinder[0].end))

View file

@ -3,7 +3,25 @@
#include "dive.h" #include "dive.h"
#include "divelist.h" #include "divelist.h"
#include <QtConcurrentMap> #include <QtConcurrentMap>
#include <QtConcurrentRun>
#include <QDir> #include <QDir>
#include <QCryptographicHash>
#include <mainwindow.h>
#include <qthelper.h>
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() DivePictureModel *DivePictureModel::instance()
{ {
@ -15,20 +33,21 @@ DivePictureModel::DivePictureModel() : numberOfPictures(0)
{ {
} }
typedef QPair<QString, QImage> SPixmap; typedef struct picture *picturepointer;
typedef QList<SPixmap> SPixmapList; typedef QPair<picturepointer, QImage> SPixmap;
typedef QList<struct picture *> SPictureList;
SPixmap scaleImages(const QString &s) SPixmap scaleImages(picturepointer picture)
{ {
static QHash <QString, QImage > cache; static QHash <QString, QImage > cache;
SPixmap ret; SPixmap ret;
ret.first = s; ret.first = picture;
if (cache.contains(s)) { if (cache.contains(picture->filename) && !cache.value(picture->filename).isNull()) {
ret.second = cache.value(s); ret.second = cache.value(picture->filename);
} else { } else {
int dim = defaultIconMetrics().sz_pic; int dim = defaultIconMetrics().sz_pic;
QImage p = QImage(s).scaled(dim, dim, Qt::KeepAspectRatio); QImage p = SHashedImage(picture).scaled(dim, dim, Qt::KeepAspectRatio);
cache.insert(s, p); cache.insert(picture->filename, p);
ret.second = p; ret.second = p;
} }
return ret; return ret;
@ -49,14 +68,15 @@ void DivePictureModel::updateDivePictures()
} }
stringPixmapCache.clear(); stringPixmapCache.clear();
QStringList pictures; SPictureList pictures;
FOR_EACH_PICTURE_NON_PTR(displayed_dive) { FOR_EACH_PICTURE_NON_PTR(displayed_dive) {
stringPixmapCache[QString(picture->filename)].offsetSeconds = picture->offset.seconds; 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<SPixmapList>(pictures, scaleImages)) QList<SPixmap> list = QtConcurrent::blockingMapped(pictures, scaleImages);
stringPixmapCache[pixmap.first].image = pixmap.second; Q_FOREACH (const SPixmap &pixmap, list)
stringPixmapCache[pixmap.first->filename].image = pixmap.second;
beginInsertRows(QModelIndex(), 0, numberOfPictures - 1); beginInsertRows(QModelIndex(), 0, numberOfPictures - 1);
endInsertRows(); endInsertRows();
@ -121,5 +141,5 @@ DivePictureWidget::DivePictureWidget(QWidget *parent) : QListView(parent)
void DivePictureWidget::doubleClicked(const QModelIndex &index) void DivePictureWidget::doubleClicked(const QModelIndex &index)
{ {
QString filePath = model()->data(index, Qt::DisplayPropertyRole).toString(); QString filePath = model()->data(index, Qt::DisplayPropertyRole).toString();
emit photoDoubleClicked(filePath); emit photoDoubleClicked(localFilePath(filePath));
} }

View file

@ -5,11 +5,18 @@
#include <QListView> #include <QListView>
#include <QThread> #include <QThread>
typedef QPair<QString, QByteArray> SHashedFilename;
struct PhotoHelper { struct PhotoHelper {
QImage image; QImage image;
int offsetSeconds; int offsetSeconds;
}; };
class SHashedImage : public QImage {
public:
SHashedImage(struct picture *picture);
};
class DivePictureModel : public QAbstractTableModel { class DivePictureModel : public QAbstractTableModel {
Q_OBJECT Q_OBJECT
public: public:

View file

@ -37,6 +37,8 @@
#endif #endif
#include <QNetworkProxy> #include <QNetworkProxy>
#include <QUndoStack> #include <QUndoStack>
#include <qthelper.h>
#include <QtConcurrentRun>
MainWindow *MainWindow::m_Instance = NULL; MainWindow *MainWindow::m_Instance = NULL;
@ -50,6 +52,7 @@ MainWindow::MainWindow() : QMainWindow(),
Q_ASSERT_X(m_Instance == NULL, "MainWindow", "MainWindow recreated!"); Q_ASSERT_X(m_Instance == NULL, "MainWindow", "MainWindow recreated!");
m_Instance = this; m_Instance = this;
ui.setupUi(this); ui.setupUi(this);
read_hashes();
// Define the States of the Application Here, Currently the states are situations where the different // Define the States of the Application Here, Currently the states are situations where the different
// widgets will change on the mainwindow. // widgets will change on the mainwindow.
@ -201,6 +204,7 @@ MainWindow::MainWindow() : QMainWindow(),
MainWindow::~MainWindow() MainWindow::~MainWindow()
{ {
write_hashes();
m_Instance = NULL; m_Instance = NULL;
} }

View file

@ -27,6 +27,11 @@
#include <QUrlQuery> #include <QUrlQuery>
#include <QEventLoop> #include <QEventLoop>
#include <QDateTime> #include <QDateTime>
#include <QSaveFile>
#include <QDir>
#include <QImageReader>
#include <QtConcurrent>
#include "divepicturewidget.h"
#include <libxslt/documents.h> #include <libxslt/documents.h>
@ -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()); ds->notes = add_to_string(ds->notes, "countrytag: %s", address.value("country").toString().toUtf8().data());
} }
} }
QHash<QString, QByteArray> hashOf;
QHash<QByteArray, QString> 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);
}

View file

@ -7,6 +7,7 @@
#include "dive.h" #include "dive.h"
#include "divelist.h" #include "divelist.h"
#include <QTranslator> #include <QTranslator>
#include <QDir>
// global pointers for our translation // global pointers for our translation
extern QTranslator *qtTranslator, *ssrfTranslator; 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); extern "C" const char *printGPSCoords(int lat, int lon);
QList<int> getDivesInTrip(dive_trip_t *trip); QList<int> getDivesInTrip(dive_trip_t *trip);
QString gasToStr(struct gasmix gas); 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 #endif // QTHELPER_H

View file

@ -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_utf8(&buf, "filename ", pic->filename, "\n");
show_gps(&buf, pic->latitude, pic->longitude); show_gps(&buf, pic->latitude, pic->longitude);
show_utf8(&buf, "hash ", pic->hash, "\n");
/* Picture loading will load even negative offsets.. */ /* Picture loading will load even negative offsets.. */
if (offset < 0) { if (offset < 0) {

View file

@ -359,6 +359,8 @@ static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer
put_format(b, " </divecomputer>\n"); put_format(b, " </divecomputer>\n");
} }
extern char * hashstring(char * filename);
static void save_picture(struct membuffer *b, struct picture *pic) static void save_picture(struct membuffer *b, struct picture *pic)
{ {
put_string(b, " <picture filename='"); put_string(b, " <picture filename='");
@ -377,6 +379,9 @@ static void save_picture(struct membuffer *b, struct picture *pic)
put_degrees(b, pic->latitude, " gps='", " "); put_degrees(b, pic->latitude, " gps='", " ");
put_degrees(b, pic->longitude, "", "'"); put_degrees(b, pic->longitude, "", "'");
} }
if (hashstring(pic->filename))
put_format(b, " hash='%s'", hashstring(pic->filename));
put_string(b, "/>\n"); put_string(b, "/>\n");
} }