subsurface/qthelper.cpp
Dirk Hohndel 1e3700c11f Don't call UI functions from helper code
This may seem much more complicated but actually is much cleaner. Add each
thread we start to the list of future results and add a new UI function
that updates the UI once all of the threads have finished.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2015-03-10 10:22:34 -07:00

873 lines
24 KiB
C++

#include "qthelper.h"
#include "helpers.h"
#include "gettextfromc.h"
#include "statistics.h"
#include "usersurvey.h"
#include "membuffer.h"
#include "subsurfacesysinfo.h"
#include "version.h"
#include "divecomputer.h"
#include "time.h"
#include "gettextfromc.h"
#include <sys/time.h>
#include <exif.h>
#include "file.h"
#include <QFile>
#include <QRegExp>
#include <QDir>
#include <QDebug>
#include <QSettings>
#include <QStandardPaths>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QUrlQuery>
#include <QEventLoop>
#include <QDateTime>
#include <QSaveFile>
#include <QDir>
#include <QImageReader>
#include <QtConcurrent>
#include "divepicturewidget.h"
#include <libxslt/documents.h>
const char *existing_filename;
static QString shortDateFormat;
static QString dateFormat;
static QString timeFormat;
static QLocale loc;
#if defined(Q_OS_WIN) && QT_VERSION < 0x050000
static QByteArray encodeUtf8(const QString &fname)
{
return fname.toUtf8();
}
static QString decodeUtf8(const QByteArray &fname)
{
return QString::fromUtf8(fname);
}
#endif
#define translate(_context, arg) trGettext(arg)
static const QString DEGREE_SIGNS("dD" UTF8_DEGREE);
QString weight_string(int weight_in_grams)
{
QString str;
if (get_units()->weight == units::KG) {
int gr = weight_in_grams % 1000;
int kg = weight_in_grams / 1000;
if (kg >= 20.0) {
str = QString("0");
} else {
str = QString("%1.%2").arg(kg).arg((unsigned)(gr) / 100);
}
} else {
double lbs = grams_to_lbs(weight_in_grams);
str = QString("%1").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1);
}
return (str);
}
extern "C" const char *printGPSCoords(int lat, int lon)
{
unsigned int latdeg, londeg;
unsigned int latmin, lonmin;
double latsec, lonsec;
QString lath, lonh, result;
if (!lat && !lon)
return strdup("");
lath = lat >= 0 ? translate("gettextFromC", "N") : translate("gettextFromC", "S");
lonh = lon >= 0 ? translate("gettextFromC", "E") : translate("gettextFromC", "W");
lat = abs(lat);
lon = abs(lon);
latdeg = lat / 1000000U;
londeg = lon / 1000000U;
latmin = (lat % 1000000U) * 60U;
lonmin = (lon % 1000000U) * 60U;
latsec = (latmin % 1000000) * 60;
lonsec = (lonmin % 1000000) * 60;
result.sprintf("%u%s%02d\'%06.3f\"%s %u%s%02d\'%06.3f\"%s",
latdeg, UTF8_DEGREE, latmin / 1000000, latsec / 1000000, lath.toUtf8().data(),
londeg, UTF8_DEGREE, lonmin / 1000000, lonsec / 1000000, lonh.toUtf8().data());
return strdup(result.toUtf8().data());
}
/**
* Try to parse in a generic manner a coordinate.
*/
static bool parseCoord(const QString& txt, int& pos, const QString& positives,
const QString& negatives, const QString& others,
double& value)
{
bool numberDefined = false, degreesDefined = false,
minutesDefined = false, secondsDefined = false;
double number = 0.0;
int posBeforeNumber = pos;
int sign = 0;
value = 0.0;
while (pos < txt.size()) {
if (txt[pos].isDigit()) {
if (numberDefined)
return false;
QRegExp numberRe("(\\d+(?:[\\.,]\\d+)?).*");
if (!numberRe.exactMatch(txt.mid(pos)))
return false;
number = numberRe.cap(1).toDouble();
numberDefined = true;
posBeforeNumber = pos;
pos += numberRe.cap(1).size() - 1;
} else if (positives.indexOf(txt[pos]) >= 0) {
if (sign != 0)
return false;
sign = 1;
if (degreesDefined || numberDefined) {
//sign after the degrees =>
//at the end of the coordinate
++pos;
break;
}
} else if (negatives.indexOf(txt[pos]) >= 0) {
if (sign != 0) {
if (others.indexOf(txt[pos]) >= 0)
//special case for the '-' sign => next coordinate
break;
return false;
}
sign = -1;
if (degreesDefined || numberDefined) {
//sign after the degrees =>
//at the end of the coordinate
++pos;
break;
}
} else if (others.indexOf(txt[pos]) >= 0) {
//we are at the next coordinate.
break;
} else if (DEGREE_SIGNS.indexOf(txt[pos]) >= 0 ||
(txt[pos].isSpace() && !degreesDefined && numberDefined)) {
if (!numberDefined)
return false;
if (degreesDefined) {
//next coordinate => need to put back the number
pos = posBeforeNumber;
numberDefined = false;
break;
}
value += number;
numberDefined = false;
degreesDefined = true;
} else if (txt[pos] == '\'' || (txt[pos].isSpace() && !minutesDefined && numberDefined)) {
if (!numberDefined || minutesDefined)
return false;
value += number / 60.0;
numberDefined = false;
minutesDefined = true;
} else if (txt[pos] == '"' || (txt[pos].isSpace() && !secondsDefined && numberDefined)) {
if (!numberDefined || secondsDefined)
return false;
value += number / 3600.0;
numberDefined = false;
secondsDefined = true;
} else {
return false;
}
++pos;
}
if (!degreesDefined && numberDefined) {
value = number; //just a single number => degrees
} else if (!minutesDefined && numberDefined) {
value += number / 60.0;
} else if (!secondsDefined && numberDefined) {
value += number / 3600.0;
} else if (numberDefined) {
return false;
}
if (sign == -1) value *= -1.0;
return true;
}
/**
* Parse special coordinate formats that cannot be handled by parseCoord.
*/
static bool parseSpecialCoords(const QString& txt, double& latitude, double& longitude) {
QRegExp xmlFormat("(-?\\d+(?:\\.\\d+)?)\\s+(-?\\d+(?:\\.\\d+)?)");
if (xmlFormat.exactMatch(txt)) {
latitude = xmlFormat.cap(1).toDouble();
longitude = xmlFormat.cap(2).toDouble();
return true;
}
return false;
}
bool parseGpsText(const QString &gps_text, double *latitude, double *longitude)
{
static const QString POS_LAT = QString("+N") + translate("gettextFromC", "N");
static const QString NEG_LAT = QString("-S") + translate("gettextFromC", "S");
static const QString POS_LON = QString("+E") + translate("gettextFromC", "E");
static const QString NEG_LON = QString("-W") + translate("gettextFromC", "W");
//remove the useless spaces (but keep the ones separating numbers)
static const QRegExp SPACE_CLEANER("\\s*([" + POS_LAT + NEG_LAT + POS_LON +
NEG_LON + DEGREE_SIGNS + "'\"\\s])\\s*");
const QString normalized = gps_text.trimmed().toUpper().replace(SPACE_CLEANER, "\\1");
if (normalized.isEmpty()) {
*latitude = 0.0;
*longitude = 0.0;
return true;
}
if (parseSpecialCoords(normalized, *latitude, *longitude))
return true;
int pos = 0;
return parseCoord(normalized, pos, POS_LAT, NEG_LAT, POS_LON + NEG_LON, *latitude) &&
parseCoord(normalized, pos, POS_LON, NEG_LON, "", *longitude) &&
pos == normalized.size();
}
#if 0 // we'll need something like this for the dive site management, eventually
bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out)
{
double latitude, longitude;
int latudeg, longudeg;
bool ignore;
bool *parsed = parsed_out ?: &ignore;
*parsed = true;
/* if we have a master and the dive's gps address is different from it,
* don't change the dive */
if (master && (master->latitude.udeg != dive->latitude.udeg ||
master->longitude.udeg != dive->longitude.udeg))
return false;
if (!(*parsed = parseGpsText(gps_text, &latitude, &longitude)))
return false;
latudeg = rint(1000000 * latitude);
longudeg = rint(1000000 * longitude);
/* if dive gps didn't change, nothing changed */
if (dive->latitude.udeg == latudeg && dive->longitude.udeg == longudeg)
return false;
/* ok, update the dive and mark things changed */
dive->latitude.udeg = latudeg;
dive->longitude.udeg = longudeg;
return true;
}
#endif
QList<int> getDivesInTrip(dive_trip_t *trip)
{
QList<int> ret;
int i;
struct dive *d;
for_each_dive (i, d) {
if (d->divetrip == trip) {
ret.push_back(get_divenr(d));
}
}
return ret;
}
// we need this to be uniq, but also make sure
// it doesn't change during the life time of a Subsurface session
// oh, and it has no meaning whatsoever - that's why we have the
// silly initial number and increment by 3 :-)
int dive_getUniqID(struct dive *d)
{
static QSet<int> ids;
static int maxId = 83529;
int id = d->id;
if (id) {
if (!ids.contains(id)) {
qDebug() << "WTF - only I am allowed to create IDs";
ids.insert(id);
}
return id;
}
maxId += 3;
id = maxId;
Q_ASSERT(!ids.contains(id));
ids.insert(id);
return id;
}
static xmlDocPtr get_stylesheet_doc(const xmlChar *uri, xmlDictPtr, int, void *, xsltLoadType)
{
QFile f(QLatin1String(":/xslt/") + (const char *)uri);
if (!f.open(QIODevice::ReadOnly))
return NULL;
/* Load and parse the data */
QByteArray source = f.readAll();
xmlDocPtr doc = xmlParseMemory(source, source.size());
return doc;
}
extern "C" xsltStylesheetPtr get_stylesheet(const char *name)
{
// this needs to be done only once, but doesn't hurt to run every time
xsltSetLoaderFunc(get_stylesheet_doc);
// get main document:
xmlDocPtr doc = get_stylesheet_doc((const xmlChar *)name, NULL, 0, NULL, XSLT_LOAD_START);
if (!doc)
return NULL;
// xsltSetGenericErrorFunc(stderr, NULL);
xsltStylesheetPtr xslt = xsltParseStylesheetDoc(doc);
if (!xslt) {
xmlFreeDoc(doc);
return NULL;
}
return xslt;
}
extern "C" void picture_load_exif_data(struct picture *p, timestamp_t *timestamp)
{
EXIFInfo exif;
memblock mem;
if (readfile(p->filename, &mem) <= 0)
goto picture_load_exit;
if (exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size) != PARSE_EXIF_SUCCESS)
goto picture_load_exit;
*timestamp = exif.epoch();
p->longitude.udeg= lrint(1000000.0 * exif.GeoLocation.Longitude);
p->latitude.udeg = lrint(1000000.0 * exif.GeoLocation.Latitude);
picture_load_exit:
free(mem.buffer);
return;
}
extern "C" const char *system_default_directory(void)
{
static char filename[PATH_MAX];
if (!*filename) {
enum QStandardPaths::StandardLocation location;
#if QT_VERSION >= 0x050400
location = QStandardPaths::AppDataLocation;
#else
location = QStandardPaths::DataLocation;
#endif
QString name = QStandardPaths::standardLocations(location).first();
QDir dir(name);
dir.mkpath(name);
// Why no "dir.encodeName()"? Crazy Qt
strncpy(filename, QFile::encodeName(name), PATH_MAX-1);
}
return filename;
}
extern "C" char *get_file_name(const char *fileName)
{
QFileInfo fileInfo(fileName);
return strdup(fileInfo.fileName().toUtf8());
}
extern "C" void copy_image_and_overwrite(const char *cfileName, const char *cnewName)
{
QString fileName = QString::fromUtf8(cfileName);
QString newName = QString::fromUtf8(cnewName);
newName += QFileInfo(cfileName).fileName();
QFile file(newName);
if (file.exists())
file.remove();
QFile::copy(fileName, newName);
}
extern "C" bool string_sequence_contains(const char *string_sequence, const char *text)
{
if (same_string(text, "") || same_string(string_sequence, ""))
return false;
QString stringSequence(string_sequence);
QStringList strings = stringSequence.split(",", QString::SkipEmptyParts);
Q_FOREACH (const QString& string, strings) {
if (string.trimmed().compare(QString(text).trimmed(), Qt::CaseInsensitive) == 0)
return true;
}
return false;
}
static bool lessThan(const QPair<QString, int> &a, const QPair<QString, int> &b)
{
return a.second < b.second;
}
void selectedDivesGasUsed(QVector<QPair<QString, int> > &gasUsedOrdered)
{
int i, j;
struct dive *d;
QMap<QString, int> gasUsed;
for_each_dive (i, d) {
if (!d->selected)
continue;
volume_t diveGases[MAX_CYLINDERS] = {};
get_gas_used(d, diveGases);
for (j = 0; j < MAX_CYLINDERS; j++)
if (diveGases[j].mliter) {
QString gasName = gasname(&d->cylinder[j].gasmix);
gasUsed[gasName] += diveGases[j].mliter;
}
}
Q_FOREACH(const QString& gas, gasUsed.keys()) {
gasUsedOrdered.append(qMakePair(gas, gasUsed[gas]));
}
qSort(gasUsedOrdered.begin(), gasUsedOrdered.end(), lessThan);
}
QString getUserAgent()
{
QString arch;
// fill in the system data - use ':' as separator
// replace all other ':' with ' ' so that this is easy to parse
QString userAgent = QString("Subsurface:%1:").arg(subsurface_version());
userAgent.append(SubsurfaceSysInfo::prettyOsName().replace(':', ' ') + ":");
arch = SubsurfaceSysInfo::buildCpuArchitecture().replace(':', ' ');
userAgent.append(arch);
if (arch == "i386")
userAgent.append("/" + SubsurfaceSysInfo::currentCpuArchitecture());
userAgent.append(":" + uiLanguage(NULL));
return userAgent;
}
QString uiLanguage(QLocale *callerLoc)
{
QSettings s;
s.beginGroup("Language");
if (!s.value("UseSystemLanguage", true).toBool()) {
loc = QLocale(s.value("UiLanguage", QLocale().uiLanguages().first()).toString());
} else {
loc = QLocale(QLocale().uiLanguages().first());
}
QString uiLang = loc.uiLanguages().first();
s.endGroup();
// there's a stupid Qt bug on MacOS where uiLanguages doesn't give us the country info
if (!uiLang.contains('-') && uiLang != loc.bcp47Name()) {
QLocale loc2(loc.bcp47Name());
loc = loc2;
uiLang = loc2.uiLanguages().first();
}
if (callerLoc)
*callerLoc = loc;
// the short format is fine
// the long format uses long weekday and month names, so replace those with the short ones
// for time we don't want the time zone designator and don't want leading zeroes on the hours
shortDateFormat = loc.dateFormat(QLocale::ShortFormat);
dateFormat = loc.dateFormat(QLocale::LongFormat);
dateFormat.replace("dddd,", "ddd").replace("dddd", "ddd").replace("MMMM", "MMM");
// special hack for Swedish as our switching from long weekday names to short weekday names
// messes things up there
dateFormat.replace("'en' 'den' d:'e'", " d");
timeFormat = loc.timeFormat();
timeFormat.replace("(t)", "").replace(" t", "").replace("t", "").replace("hh", "h").replace("HH", "H").replace("'kl'.", "");
timeFormat.replace(".ss", "").replace(":ss", "").replace("ss", "");
return uiLang;
}
QLocale getLocale()
{
return loc;
}
QString getDateFormat()
{
return dateFormat;
}
void set_filename(const char *filename, bool force)
{
if (!force && existing_filename)
return;
free((void *)existing_filename);
if (filename)
existing_filename = strdup(filename);
else
existing_filename = NULL;
}
const QString get_dc_nickname(const char *model, uint32_t deviceid)
{
const DiveComputerNode *existNode = dcList.getExact(model, deviceid);
if (existNode && !existNode->nickName.isEmpty())
return existNode->nickName;
else
return model;
}
QString get_depth_string(int mm, bool showunit, bool showdecimal)
{
if (prefs.units.length == units::METERS) {
double meters = mm / 1000.0;
return QString("%1%2").arg(meters, 0, 'f', (showdecimal && meters < 20.0) ? 1 : 0).arg(showunit ? translate("gettextFromC", "m") : "");
} else {
double feet = mm_to_feet(mm);
return QString("%1%2").arg(feet, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "ft") : "");
}
}
QString get_depth_string(depth_t depth, bool showunit, bool showdecimal)
{
return get_depth_string(depth.mm, showunit, showdecimal);
}
QString get_depth_unit()
{
if (prefs.units.length == units::METERS)
return QString("%1").arg(translate("gettextFromC", "m"));
else
return QString("%1").arg(translate("gettextFromC", "ft"));
}
QString get_weight_string(weight_t weight, bool showunit)
{
QString str = weight_string(weight.grams);
if (get_units()->weight == units::KG) {
str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "kg") : "");
} else {
str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "lbs") : "");
}
return (str);
}
QString get_weight_unit()
{
if (prefs.units.weight == units::KG)
return QString("%1").arg(translate("gettextFromC", "kg"));
else
return QString("%1").arg(translate("gettextFromC", "lbs"));
}
/* these methods retrieve used gas per cylinder */
static unsigned start_pressure(cylinder_t *cyl)
{
return cyl->start.mbar ?: cyl->sample_start.mbar;
}
static unsigned end_pressure(cylinder_t *cyl)
{
return cyl->end.mbar ?: cyl->sample_end.mbar;
}
QString get_cylinder_used_gas_string(cylinder_t *cyl, bool showunit)
{
int decimals;
const char *unit;
double gas_usage;
/* Get the cylinder gas use in mbar */
gas_usage = start_pressure(cyl) - end_pressure(cyl);
/* Can we turn it into a volume? */
if (cyl->type.size.mliter) {
gas_usage = bar_to_atm(gas_usage / 1000);
gas_usage *= cyl->type.size.mliter;
gas_usage = get_volume_units(gas_usage, &decimals, &unit);
} else {
gas_usage = get_pressure_units(gas_usage, &unit);
decimals = 0;
}
// translate("gettextFromC","%.*f %s"
return QString("%1 %2").arg(gas_usage, 0, 'f', decimals).arg(showunit ? unit : "");
}
QString get_temperature_string(temperature_t temp, bool showunit)
{
if (temp.mkelvin == 0) {
return ""; //temperature not defined
} else if (prefs.units.temperature == units::CELSIUS) {
double celsius = mkelvin_to_C(temp.mkelvin);
return QString("%1%2%3").arg(celsius, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "C") : "");
} else {
double fahrenheit = mkelvin_to_F(temp.mkelvin);
return QString("%1%2%3").arg(fahrenheit, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "F") : "");
}
}
QString get_temp_unit()
{
if (prefs.units.temperature == units::CELSIUS)
return QString(UTF8_DEGREE "C");
else
return QString(UTF8_DEGREE "F");
}
QString get_volume_string(volume_t volume, bool showunit, int mbar)
{
const char *unit;
int decimals;
double value = get_volume_units(volume.mliter, &decimals, &unit);
if (mbar) {
// we are showing a tank size
// fix the weird imperial way of denominating size and provide
// reasonable number of decimals
if (prefs.units.volume == units::CUFT)
value *= bar_to_atm(mbar / 1000.0);
decimals = (value > 20.0) ? 0 : (value > 2.0) ? 1 : 2;
}
return QString("%1%2").arg(value, 0, 'f', decimals).arg(showunit ? unit : "");
}
QString get_volume_unit()
{
const char *unit;
(void) get_volume_units(0, NULL, &unit);
return QString(unit);
}
QString get_pressure_string(pressure_t pressure, bool showunit)
{
if (prefs.units.pressure == units::BAR) {
double bar = pressure.mbar / 1000.0;
return QString("%1%2").arg(bar, 0, 'f', 1).arg(showunit ? translate("gettextFromC", "bar") : "");
} else {
double psi = mbar_to_PSI(pressure.mbar);
return QString("%1%2").arg(psi, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "psi") : "");
}
}
QString getSubsurfaceDataPath(QString folderToFind)
{
QString execdir;
QDir folder;
// first check if we are running in the build dir, so the path that we
// are looking for is just a subdirectory of the execution path;
// this also works on Windows as there we install the dirs
// under the application path
execdir = QCoreApplication::applicationDirPath();
folder = QDir(execdir.append(QDir::separator()).append(folderToFind));
if (folder.exists())
return folder.absolutePath();
// next check for the Linux typical $(prefix)/share/subsurface
execdir = QCoreApplication::applicationDirPath();
if (execdir.contains("bin")) {
folder = QDir(execdir.replace("bin", "share/subsurface/").append(folderToFind));
if (folder.exists())
return folder.absolutePath();
}
// then look for the usual locations on a Mac
execdir = QCoreApplication::applicationDirPath();
folder = QDir(execdir.append("/../Resources/share/").append(folderToFind));
if (folder.exists())
return folder.absolutePath();
execdir = QCoreApplication::applicationDirPath();
folder = QDir(execdir.append("/../Resources/").append(folderToFind));
if (folder.exists())
return folder.absolutePath();
return QString("");
}
int gettimezoneoffset(timestamp_t when)
{
QDateTime dt1, dt2;
if (when == 0)
dt1 = QDateTime::currentDateTime();
else
dt1 = QDateTime::fromMSecsSinceEpoch(when * 1000);
dt2 = dt1.toUTC();
dt1.setTimeSpec(Qt::UTC);
return dt2.secsTo(dt1);
}
int parseTemperatureToMkelvin(const QString &text)
{
int mkelvin;
QString numOnly = text;
numOnly.replace(",", ".").remove(QRegExp("[^-0-9.]"));
if (numOnly.isEmpty())
return 0;
double number = numOnly.toDouble();
switch (prefs.units.temperature) {
case units::CELSIUS:
mkelvin = C_to_mkelvin(number);
break;
case units::FAHRENHEIT:
mkelvin = F_to_mkelvin(number);
break;
default:
mkelvin = 0;
}
return mkelvin;
}
QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText)
{
int hrs, mins;
mins = (when + 59) / 60;
hrs = mins / 60;
mins -= hrs * 60;
QString displayTime;
if (hrs)
displayTime = QString("%1%2%3%4").arg(hrs).arg(hourText).arg(mins, 2, 10, QChar('0')).arg(minutesText);
else
displayTime = QString("%1%2").arg(mins).arg(minutesText);
return displayTime;
}
QString get_dive_date_string(timestamp_t when)
{
QDateTime ts;
ts.setMSecsSinceEpoch(when * 1000L);
return loc.toString(ts.toUTC(), dateFormat + " " + timeFormat);
}
QString get_short_dive_date_string(timestamp_t when)
{
QDateTime ts;
ts.setMSecsSinceEpoch(when * 1000L);
return loc.toString(ts.toUTC(), shortDateFormat + " " + timeFormat);
}
const char *get_dive_date_c_string(timestamp_t when)
{
QString text = get_dive_date_string(when);
return strdup(text.toUtf8().data());
}
QString get_trip_date_string(timestamp_t when, int nr)
{
struct tm tm;
utc_mkdate(when, &tm);
if (nr != 1) {
QString ret = translate("gettextFromC", "%1 %2 (%3 dives)");
return ret.arg(monthname(tm.tm_mon))
.arg(tm.tm_year + 1900)
.arg(nr);
} else {
QString ret = translate("gettextFromC", "%1 %2 (1 dive)");
return ret.arg(monthname(tm.tm_mon))
.arg(tm.tm_year + 1900);
}
}
extern "C" void reverseGeoLookup(degrees_t latitude, degrees_t longitude, uint32_t uuid)
{
QNetworkRequest request;
QNetworkAccessManager *rgl = new QNetworkAccessManager();
request.setUrl(QString("http://open.mapquestapi.com/nominatim/v1/reverse.php?format=json&accept-language=%1&lat=%2&lon=%3")
.arg(uiLanguage(NULL)).arg(latitude.udeg / 1000000.0).arg(longitude.udeg / 1000000.0));
request.setRawHeader("Accept", "text/json");
request.setRawHeader("User-Agent", getUserAgent().toUtf8());
QNetworkReply *reply = rgl->get(request);
QEventLoop loop;
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
QJsonParseError errorObject;
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &errorObject);
if (errorObject.error != QJsonParseError::NoError) {
qDebug() << errorObject.errorString();
} else {
QJsonObject obj = jsonDoc.object();
QJsonObject address = obj.value("address").toObject();
qDebug() << "found country:" << address.value("country").toString();
struct dive_site *ds = get_dive_site_by_uuid(uuid);
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);
}
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);
}