mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-28 05:00:20 +00:00
5fae7ce7a0
Include unused tanks in merges of multiple logs into a single dive if the 'Show unused cylinders' preference is enabled. Also rename the preference (in code) to `include_unused_tanks` to reflect the fact that it is already used in more places than just the display (exporting, cloning dives). Simplified the cylinder model to make forced inclusion of unused tanks dependent on use of the model in planner. Leaving the persisted name of the preference as `display_unused_tanks` to avoid resetting this for all users - is there a good way to migrate preference names? Signed-off-by: Michael Keller <github@ike.ch>
360 lines
9.6 KiB
C++
360 lines
9.6 KiB
C++
// SPDX-License-Identifier: GPL-2.0
|
|
#include "uploadDiveLogsDE.h"
|
|
#include <QDir>
|
|
#include <QDebug>
|
|
#include <QTemporaryFile>
|
|
#include <zip.h>
|
|
#include <errno.h>
|
|
#include "core/errorhelper.h"
|
|
#include "core/qthelper.h"
|
|
#include "core/dive.h"
|
|
#include "core/membuffer.h"
|
|
#include "core/divesite.h"
|
|
#include "core/cloudstorage.h"
|
|
#include "core/xmlparams.h"
|
|
#ifndef SUBSURFACE_MOBILE
|
|
#include "core/selection.h"
|
|
#endif // SUBSURFACE_MOBILE
|
|
#include "core/settings/qPrefCloudStorage.h"
|
|
|
|
|
|
uploadDiveLogsDE *uploadDiveLogsDE::instance()
|
|
{
|
|
static uploadDiveLogsDE *self = new uploadDiveLogsDE;
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
uploadDiveLogsDE::uploadDiveLogsDE():
|
|
reply(NULL),
|
|
multipart(NULL)
|
|
{
|
|
timeout.setSingleShot(true);
|
|
}
|
|
|
|
|
|
static QString makeTempFileName()
|
|
{
|
|
QTemporaryFile tmpfile;
|
|
tmpfile.setFileTemplate(QDir::tempPath() + "/divelogsde-upload.XXXXXXXX.dld");
|
|
tmpfile.open();
|
|
return tmpfile.fileName();
|
|
}
|
|
|
|
|
|
void uploadDiveLogsDE::doUpload(bool selected, const QString &userid, const QString &password)
|
|
{
|
|
QString err;
|
|
|
|
QString filename = makeTempFileName();
|
|
|
|
// Make zip file, with all dives, in divelogs.de format
|
|
if (!prepareDives(filename, selected)) {
|
|
emit uploadFinish(false, tr("Cannot prepare dives, none selected?"));
|
|
timeout.stop();
|
|
return;
|
|
}
|
|
|
|
// And upload it
|
|
uploadDives(filename, userid, password);
|
|
}
|
|
|
|
|
|
bool uploadDiveLogsDE::prepareDives(const QString &tempfile, bool selected)
|
|
{
|
|
static const char errPrefix[] = "divelog.de-upload:";
|
|
|
|
xsltStylesheetPtr xslt = NULL;
|
|
struct zip *zip;
|
|
|
|
emit uploadStatus(tr("building zip file to upload"));
|
|
|
|
xslt = get_stylesheet("divelogs-export.xslt");
|
|
if (!xslt) {
|
|
qDebug() << errPrefix << "missing stylesheet";
|
|
report_error(tr("Stylesheet to export to divelogs.de is not found").toUtf8());
|
|
return false;
|
|
}
|
|
|
|
// Prepare zip file
|
|
int error_code;
|
|
zip = zip_open(QFile::encodeName(QDir::toNativeSeparators(tempfile)), ZIP_CREATE, &error_code);
|
|
if (!zip) {
|
|
char buffer[1024];
|
|
zip_error_to_str(buffer, sizeof buffer, error_code, errno);
|
|
report_error(tr("Failed to create zip file for upload: %s").toUtf8(), buffer);
|
|
return false;
|
|
}
|
|
|
|
/* walk the dive list in chronological order */
|
|
int i;
|
|
struct dive *dive;
|
|
for_each_dive (i, dive) {
|
|
char filename[PATH_MAX];
|
|
int streamsize;
|
|
char *membuf;
|
|
xmlDoc *transformed;
|
|
struct zip_source *s;
|
|
struct membufferpp mb;
|
|
struct xml_params *params = alloc_xml_params();
|
|
|
|
/*
|
|
* Get the i'th dive in XML format so we can process it.
|
|
* We need to save to a file before we can reload it back into memory...
|
|
*/
|
|
if (selected && !dive->selected) {
|
|
free_xml_params(params);
|
|
continue;
|
|
}
|
|
|
|
/* add the dive */
|
|
struct dive_site *ds = dive->dive_site;
|
|
|
|
if (ds) {
|
|
put_format(&mb, "<divelog><divesites><site uuid='%8x' name='", ds->uuid);
|
|
put_quoted(&mb, ds->name, 1, 0);
|
|
put_format(&mb, "'");
|
|
put_location(&mb, &ds->location, " gps='", "'");
|
|
put_format(&mb, ">\n");
|
|
if (ds->taxonomy.nr) {
|
|
for (int j = 0; j < ds->taxonomy.nr; j++) {
|
|
struct taxonomy *t = &ds->taxonomy.category[j];
|
|
if (t->category != TC_NONE && t->category == prefs.geocoding.category[j] && t->value) {
|
|
put_format(&mb, " <geo cat='%d'", t->category);
|
|
put_format(&mb, " origin='%d' value='", t->origin);
|
|
put_quoted(&mb, t->value, 1, 0);
|
|
put_format(&mb, "'/>\n");
|
|
}
|
|
}
|
|
}
|
|
put_format(&mb, "</site>\n</divesites>\n");
|
|
}
|
|
|
|
save_one_dive_to_mb(&mb, dive, false);
|
|
|
|
if (ds) {
|
|
put_format(&mb, "</divelog>\n");
|
|
}
|
|
/*
|
|
* Parse the memory buffer into XML document and
|
|
* transform it to divelogs.de format, finally dumping
|
|
* the XML into a character buffer.
|
|
*/
|
|
xmlDoc *doc = xmlReadMemory(mb.buffer, mb.len, "divelog", NULL, XML_PARSE_HUGE | XML_PARSE_RECOVER);
|
|
if (!doc) {
|
|
qWarning() << errPrefix << "could not parse back into memory the XML file we've just created!";
|
|
report_error(tr("internal error").toUtf8());
|
|
zip_close(zip);
|
|
QFile::remove(tempfile);
|
|
xsltFreeStylesheet(xslt);
|
|
free_xml_params(params);
|
|
return false;
|
|
}
|
|
|
|
xml_params_add_int(params, "allcylinders", prefs.include_unused_tanks);
|
|
transformed = xsltApplyStylesheet(xslt, doc, xml_params_get(params));
|
|
free_xml_params(params);
|
|
if (!transformed) {
|
|
qWarning() << errPrefix << "XSLT transform failed for dive: " << i;
|
|
report_error(tr("Conversion of dive %1 to divelogs.de format failed").arg(i).toUtf8());
|
|
continue;
|
|
}
|
|
xmlDocDumpMemory(transformed, (xmlChar **)&membuf, &streamsize);
|
|
xmlFreeDoc(doc);
|
|
xmlFreeDoc(transformed);
|
|
|
|
/*
|
|
* Save the XML document into a zip file.
|
|
*/
|
|
snprintf(filename, PATH_MAX, "%d.xml", i + 1);
|
|
s = zip_source_buffer(zip, membuf, streamsize, 1); // frees membuffer!
|
|
if (s) {
|
|
int64_t ret = zip_add(zip, filename, s);
|
|
if (ret == -1)
|
|
qDebug() << errPrefix << "failed to include dive:" << i;
|
|
}
|
|
}
|
|
xsltFreeStylesheet(xslt);
|
|
if (zip_close(zip)) {
|
|
int ze, se;
|
|
#if LIBZIP_VERSION_MAJOR >= 1
|
|
zip_error_t *error = zip_get_error(zip);
|
|
ze = zip_error_code_zip(error);
|
|
se = zip_error_code_system(error);
|
|
#else
|
|
zip_error_get(zip, &ze, &se);
|
|
#endif
|
|
report_error(qPrintable(tr("error writing zip file: %s zip error %d system error %d - %s")),
|
|
qPrintable(QDir::toNativeSeparators(tempfile)), ze, se, zip_strerror(zip));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void uploadDiveLogsDE::cleanupTempFile()
|
|
{
|
|
if (tempFile.isOpen())
|
|
tempFile.remove();
|
|
}
|
|
|
|
|
|
void uploadDiveLogsDE::uploadDives(const QString &filename, const QString &userid, const QString &password)
|
|
{
|
|
QHttpPart part1, part2, part3;
|
|
static QNetworkRequest request;
|
|
QString args;
|
|
|
|
// Check if there is an earlier request open
|
|
if (reply != NULL && reply->isOpen()) {
|
|
reply->abort();
|
|
delete reply;
|
|
reply = NULL;
|
|
}
|
|
if (multipart != NULL) {
|
|
delete multipart;
|
|
multipart = NULL;
|
|
}
|
|
multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
|
|
|
emit uploadStatus(tr("Uploading dives"));
|
|
|
|
// prepare header with filename (of all dives) and pointer to file
|
|
args = "form-data; name=\"userfile\"; filename=\"" + filename + "\"";
|
|
part1.setRawHeader("Content-Disposition", args.toLatin1());
|
|
|
|
// open new file for reading
|
|
cleanupTempFile();
|
|
tempFile.setFileName(filename);
|
|
if (!tempFile.open(QIODevice::ReadOnly)) {
|
|
qDebug() << "ERROR opening zip file: " << filename;
|
|
return;
|
|
}
|
|
part1.setBodyDevice(&tempFile);
|
|
multipart->append(part1);
|
|
|
|
// Add userid
|
|
args = "form-data; name=\"user\"";
|
|
part2.setRawHeader("Content-Disposition", args.toLatin1());
|
|
part2.setBody(qPrefCloudStorage::divelogde_user().toUtf8());
|
|
multipart->append(part2);
|
|
|
|
// Add password
|
|
args = "form-data; name=\"pass\"";
|
|
part3.setRawHeader("Content-Disposition", args.toLatin1());
|
|
part3.setBody(qPrefCloudStorage::divelogde_pass().toUtf8());
|
|
multipart->append(part3);
|
|
|
|
// Prepare network request
|
|
request.setUrl(QUrl("https://divelogs.de/DivelogsDirectImport.php"));
|
|
request.setRawHeader("Accept", "text/xml, application/xml");
|
|
request.setRawHeader("User-Agent", getUserAgent().toUtf8());
|
|
|
|
// Execute async.
|
|
reply = manager()->post(request, multipart);
|
|
|
|
// connect signals from upload process
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
connect(reply, &QNetworkReply::finished, this, &uploadDiveLogsDE::uploadFinishedSlot);
|
|
connect(reply, &QNetworkReply::errorOccurred, this, &uploadDiveLogsDE::uploadErrorSlot);
|
|
connect(reply, &QNetworkReply::uploadProgress, this, &uploadDiveLogsDE::updateProgressSlot);
|
|
connect(&timeout, &QTimer::timeout, this, &uploadDiveLogsDE::uploadTimeoutSlot);
|
|
#else
|
|
|
|
connect(reply, SIGNAL(finished()), this, SLOT(uploadFinishedSlot()));
|
|
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this,
|
|
SLOT(uploadErrorSlot(QNetworkReply::NetworkError)));
|
|
connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this,
|
|
SLOT(updateProgressSlot(qint64, qint64)));
|
|
connect(&timeout, SIGNAL(timeout()), this, SLOT(uploadTimeoutSlot()));
|
|
#endif
|
|
timeout.start(30000); // 30s
|
|
}
|
|
|
|
|
|
void uploadDiveLogsDE::updateProgressSlot(qint64 current, qint64 total)
|
|
{
|
|
if (!reply)
|
|
return;
|
|
|
|
if (total <= 0 || current <= 0)
|
|
return;
|
|
|
|
// Calculate percentage as 0.x (values between 0.0 and 1.0)
|
|
// And signal whoever wants to know
|
|
qreal percentage = (float)current / (float)total;
|
|
emit uploadProgress(percentage, 1.0);
|
|
|
|
// reset the timer: 30 seconds after we last got any data
|
|
timeout.start();
|
|
}
|
|
|
|
|
|
void uploadDiveLogsDE::uploadFinishedSlot()
|
|
{
|
|
QString err;
|
|
|
|
if (!reply)
|
|
return;
|
|
|
|
// check what the server sent us: it might contain
|
|
// an error condition, such as a failed login
|
|
QByteArray xmlData = reply->readAll();
|
|
reply->deleteLater();
|
|
reply = NULL;
|
|
cleanupTempFile();
|
|
char *resp = xmlData.data();
|
|
if (resp) {
|
|
char *parsed = strstr(resp, "<Login>");
|
|
if (parsed) {
|
|
if (strstr(resp, "<Login>succeeded</Login>")) {
|
|
if (strstr(resp, "<FileCopy>failed</FileCopy>")) {
|
|
report_error(tr("Upload failed").toUtf8());
|
|
return;
|
|
}
|
|
timeout.stop();
|
|
err = tr("Upload successful");
|
|
emit uploadFinish(true, err);
|
|
return;
|
|
}
|
|
timeout.stop();
|
|
err = tr("Login failed");
|
|
report_error(err.toUtf8());
|
|
emit uploadFinish(false, err);
|
|
return;
|
|
}
|
|
timeout.stop();
|
|
err = tr("Cannot parse response");
|
|
report_error(tr("Cannot parse response").toUtf8());
|
|
emit uploadFinish(false, err);
|
|
}
|
|
}
|
|
|
|
|
|
void uploadDiveLogsDE::uploadTimeoutSlot()
|
|
{
|
|
timeout.stop();
|
|
if (reply) {
|
|
reply->deleteLater();
|
|
reply = NULL;
|
|
}
|
|
cleanupTempFile();
|
|
QString err(tr("divelogs.de not responding"));
|
|
report_error(err.toUtf8());
|
|
emit uploadFinish(false, err);
|
|
}
|
|
|
|
|
|
void uploadDiveLogsDE::uploadErrorSlot(QNetworkReply::NetworkError error)
|
|
{
|
|
timeout.stop();
|
|
if (reply) {
|
|
reply->deleteLater();
|
|
reply = NULL;
|
|
}
|
|
cleanupTempFile();
|
|
QString err(tr("network error %1").arg(error));
|
|
report_error(err.toUtf8());
|
|
emit uploadFinish(false, err);
|
|
}
|