mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
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:
parent
ea00fdb36a
commit
b02bf002a6
11 changed files with 139 additions and 15 deletions
2
dive.c
2
dive.c
|
@ -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
1
dive.h
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
69
qthelper.cpp
69
qthelper.cpp
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue