Hi,
From 560443728da15c70b73c4cca059c3d1a6dc2a006 Mon Sep 17 00:00:00 2001 From: "Robert C. Helling" <[email protected]> Date: Thu, 26 Feb 2015 14:39:42 +0100 Subject: [PATCH 1/2] 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 <[email protected]> --- dive.c | 2 ++ dive.h | 1 + load-git.c | 8 ++++- parse-xml.c | 2 ++ qt-ui/divepicturewidget.cpp | 46 ++++++++++++++++++++-------- qt-ui/divepicturewidget.h | 7 +++++ qt-ui/mainwindow.cpp | 4 +++ qthelper.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++ qthelper.h | 9 +++++- save-git.c | 1 + save-xml.c | 5 ++++ 11 files changed, 143 insertions(+), 15 deletions(-) diff --git a/dive.c b/dive.c index 7583686..fc0fbb8 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 8247a5f..653b113 100644 --- a/dive.h +++ b/dive.h @@ -364,6 +364,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 053a714..be25e8e 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 3c5bfe0..5e6dc5c 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -1294,6 +1294,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 92695b6..a0d209b 100644 --- a/qt-ui/divepicturewidget.cpp +++ b/qt-ui/divepicturewidget.cpp @@ -3,7 +3,25 @@ #include "dive.h" #include "divelist.h" #include <QtConcurrentMap> +#include <QtConcurrentRun> #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() { @@ -15,20 +33,21 @@ DivePictureModel::DivePictureModel() : numberOfPictures(0) { } -typedef QPair<QString, QImage> SPixmap; -typedef QList<SPixmap> SPixmapList; +typedef struct picture *picturepointer; +typedef QPair<picturepointer, QImage> SPixmap; +typedef QList<struct picture *> SPictureList; -SPixmap scaleImages(const QString &s) +SPixmap scaleImages(picturepointer picture) { static QHash <QString, QImage > 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<SPixmapList>(pictures, scaleImages)) - stringPixmapCache[pixmap.first].image = pixmap.second; + QList<SPixmap> 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 aa524e1..e8104a1 100644 --- a/qt-ui/divepicturewidget.h +++ b/qt-ui/divepicturewidget.h @@ -5,11 +5,18 @@ #include <QListView> #include <QThread> +typedef QPair<QString, QByteArray> 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 648ead1..2c62106 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -37,6 +37,8 @@ #endif #include <QNetworkProxy> #include <QUndoStack> +#include <qthelper.h> +#include <QtConcurrentRun> 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 b26bdf4..17baa7f 100644 --- a/qthelper.cpp +++ b/qthelper.cpp @@ -21,6 +21,11 @@ #include <QNetworkAccessManager> #include <QUrlQuery> #include <QEventLoop> +#include <QSaveFile> +#include <QDir> +#include <QImageReader> +#include <QtConcurrent> +#include "divepicturewidget.h" #include <libxslt/documents.h> @@ -401,3 +406,71 @@ 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<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); +} + + + + diff --git a/qthelper.h b/qthelper.h index a367a9d..e164a41 100644 --- a/qthelper.h +++ b/qthelper.h @@ -7,6 +7,7 @@ #include "dive.h" #include "divelist.h" #include <QTranslator> +#include <QDir> // 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<int> 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 0125f64..a7b5144 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 8366f87..c68b5b6 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, " </divecomputer>\n"); } +extern char * hashstring(char * filename); + static void save_picture(struct membuffer *b, struct picture *pic) { 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->longitude, "", "'"); } + if (hashstring(pic->filename)) + put_format(b, " hash='%s'", hashstring(pic->filename)); + put_string(b, "/>\n"); } -- 1.9.3 (Apple Git-50)
From a7bcdf337270ff5823fbb8cda3c097d61e0d97c4 Mon Sep 17 00:00:00 2001 From: "Robert C. Helling" <[email protected]> Date: Thu, 26 Feb 2015 14:44:27 +0100 Subject: [PATCH 2/2] UI to learn hashes of local image files This addes a menu entry for the user to select a directory that is recursively traversed to look for image files and compute the hashes of those images (for those images to be available to be displayed in dives according to their hash values). This traversal and hash computation happens in and independend thread and so far the only feedback to the user is that upon completion the dispayed images are updated. Signed-off-by: Robert C. Helling <[email protected]> --- qt-ui/mainwindow.cpp | 18 ++++++++++++++++++ qt-ui/mainwindow.h | 1 + qt-ui/mainwindow.ui | 8 +++++++- qthelper.cpp | 23 +++++++++++++++++++++++ qthelper.h | 1 + 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 2c62106..b19cb8a 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -319,6 +319,24 @@ void MainWindow::on_actionSaveAs_triggered() file_save_as(); } +void MainWindow::on_actionHash_images_triggered() +{ + QFileDialog dialog(this, tr("Traverse image directories"), lastUsedDir(), filter()); + dialog.setFileMode(QFileDialog::Directory); + dialog.setViewMode(QFileDialog::Detail); + dialog.setLabelText(QFileDialog::Accept, tr("Scan")); + dialog.setLabelText(QFileDialog::Reject, tr("Cancel")); + QStringList dirnames; + if (dialog.exec()) + dirnames = dialog.selectedFiles(); + if (dirnames.isEmpty()) + return; + foreach (QString dir, dirnames) { + QtConcurrent::run(learnImages, QDir(dir), 10, false); + } + +} + ProfileWidget2 *MainWindow::graphics() const { return qobject_cast<ProfileWidget2*>(applicationState["Default"].topRight->layout()->itemAt(1)->widget()); diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h index 85a6312..b5d1cad 100644 --- a/qt-ui/mainwindow.h +++ b/qt-ui/mainwindow.h @@ -103,6 +103,7 @@ slots: void on_actionPrint_triggered(); void on_actionPreferences_triggered(); void on_actionQuit_triggered(); + void on_actionHash_images_triggered(); /* log menu actions */ void on_actionDownloadDC_triggered(); diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui index b6cd4a3..41a85bf 100644 --- a/qt-ui/mainwindow.ui +++ b/qt-ui/mainwindow.ui @@ -50,7 +50,7 @@ <x>0</x> <y>0</y> <width>861</width> - <height>25</height> + <height>22</height> </rect> </property> <widget class="QMenu" name="menuFile"> @@ -67,6 +67,7 @@ <addaction name="actionPrint"/> <addaction name="actionPreferences"/> <addaction name="separator"/> + <addaction name="actionHash_images"/> <addaction name="actionConfigure_Dive_Computer"/> <addaction name="separator"/> <addaction name="actionRecent1"/> @@ -690,6 +691,11 @@ <string>Ctrl+Shift+Z</string> </property> </action> + <action name="actionHash_images"> + <property name="text"> + <string>Hash images</string> + </property> + </action> </widget> <customwidgets> <customwidget> diff --git a/qthelper.cpp b/qthelper.cpp index 17baa7f..35e45ed 100644 --- a/qthelper.cpp +++ b/qthelper.cpp @@ -474,3 +474,26 @@ void updateHash(struct picture *picture) { +void learnImages(const QDir dir, int max_recursions, bool recursed) +{ + QDir current(dir); + QStringList filters, files; + + if (max_recursions) { + foreach (QString dirname, dir.entryList(QStringList(), QDir::NoDotAndDotDot | QDir::Dirs)) { + learnImages(QDir(dir.filePath(dirname)), max_recursions - 1, true); + } + } + + foreach (QString format, QImageReader::supportedImageFormats()) { + filters.append(QString("*.").append(format)); + } + + foreach (QString file, dir.entryList(filters, QDir::Files)) { + files.append(dir.absoluteFilePath(file)); + } + + QtConcurrent::blockingMap(files, hashFile); + if (!recursed) + DivePictureModel::instance()->updateDivePictures(); +} diff --git a/qthelper.h b/qthelper.h index e164a41..8b553ba 100644 --- a/qthelper.h +++ b/qthelper.h @@ -21,6 +21,7 @@ void read_hashes(); void write_hashes(); void updateHash(struct picture *picture); QByteArray hashFile(const QString filename); +void learnImages(const QDir dir, int max_recursions, bool recursed); void add_hash(const QString filename, QByteArray &hash); QString localFilePath(const QString originalFilename); QString fileFromHash(char *hash); -- 1.9.3 (Apple Git-50)
this is my second attempt at providing patches to add hash information to images (with hopefully much clearer API and variable names, thanks for the suggestions!). As a byproduct, I learned about the use of the -p option for git add. These patches should help with images for dive logs maintained on different computers (with different paths for image directories etc). I imagine the workflow as follows: you add images to your dives as before. But now hashes are computed for each displayed image (so to work with an old log, you should go through the log and display all images and then save the file again). Then you can transfer all you image files to the other computer (with scp, a flash drive or some cloud service like dropbox or possibly git). You don’t have to maintain the same directory structure as on the original computer. Then in the file menu you choose „Hash images“ and select a directory that contains (possibly in subdirectories) your image files. After a little while, subsurface will have learned the locations and hashes of all those image files and will display the images based on hashes also on the second computer. Best Robert
signature.asc
Description: Message signed with OpenPGP using GPGMail
_______________________________________________ subsurface mailing list [email protected] http://lists.subsurface-divelog.org/cgi-bin/mailman/listinfo/subsurface
