mirror of
				https://github.com/subsurface/subsurface.git
				synced 2025-02-19 22:16:15 +00:00 
			
		
		
		
	core: add upload divelogs.de class
The implementation is based on class DivelogsDeWebServices in desktop-widgets but without the UI entanglement Signed-off-by: Jan Iversen <jan@casacondor.com>
This commit is contained in:
		
							parent
							
								
									42939ab7f8
								
							
						
					
					
						commit
						eb08f17a03
					
				
					 3 changed files with 358 additions and 0 deletions
				
			
		|  | @ -167,6 +167,8 @@ set(SUBSURFACE_CORE_LIB_SRCS | |||
| 	units.c | ||||
| 	uploadDiveShare.cpp | ||||
| 	uploadDiveShare.h | ||||
| 	uploadDiveLogsDE.cpp | ||||
| 	uploadDiveLogsDE.h | ||||
| 	version.c | ||||
| 	version.h | ||||
| 	videoframeextractor.cpp | ||||
|  |  | |||
							
								
								
									
										320
									
								
								core/uploadDiveLogsDE.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								core/uploadDiveLogsDE.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,320 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| #include "uploadDiveLogsDE.h" | ||||
| #include <QDir> | ||||
| #include <QDebug> | ||||
| #include <zip.h> | ||||
| #include <errno.h> | ||||
| #include "core/display.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/settings/qPrefCloudStorage.h" | ||||
| 
 | ||||
| 
 | ||||
| uploadDiveLogsDE *uploadDiveLogsDE::instance() | ||||
| { | ||||
| 	static uploadDiveLogsDE *self = new uploadDiveLogsDE; | ||||
| 
 | ||||
| 	return self; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| uploadDiveLogsDE::uploadDiveLogsDE(): | ||||
| 	reply(NULL), | ||||
| 	multipart(NULL) | ||||
| { | ||||
| 	timeout.setSingleShot(true); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void uploadDiveLogsDE::doUpload(bool selected, const QString &userid, const QString &password) | ||||
| { | ||||
| 	QString err; | ||||
| 
 | ||||
| 	/* generate a temporary filename and create/open that file with zip_open */ | ||||
| 	QString filename(QDir::tempPath() + "/divelogsde-upload.dld"); | ||||
| 
 | ||||
| 	// delete file if it exist
 | ||||
| 	QFile f(filename); | ||||
| 	if (f.open(QIODevice::ReadOnly)) { | ||||
| 		f.close(); | ||||
| 		f.remove(); | ||||
| 	} | ||||
| 
 | ||||
| 	// Make zip file, with all dives, in divelogs.de format
 | ||||
| 	if (!prepareDives(selected, filename)) { | ||||
| 		err = tr("Failed to create uploadDiveLogsDE file %s\n").arg(filename); | ||||
| 		report_error(err.toUtf8()); | ||||
| 		emit uploadFinish(false, err); | ||||
| 		timeout.stop(); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	// And upload it
 | ||||
| 	uploadDives(filename, userid, password); | ||||
| 	timeout.stop(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool uploadDiveLogsDE::prepareDives(bool selected, const QString &filename) | ||||
| { | ||||
| 	static const char errPrefix[] = "divelog.de-uploadDiveLogsDE:"; | ||||
| 	xsltStylesheetPtr xslt = NULL; | ||||
| 	struct zip *zip; | ||||
| 
 | ||||
| 	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(filename)), 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 uploadDiveLogsDE: %s").toUtf8(), buffer); | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	/* walk the dive list in chronological order */ | ||||
| 	int i; | ||||
| 	struct dive *dive; | ||||
| 	for_each_dive (i, dive) { | ||||
| 		char xmlfilename[PATH_MAX]; | ||||
| 		int streamsize; | ||||
| 		const char *membuf; | ||||
| 		xmlDoc *transformed; | ||||
| 		struct zip_source *s; | ||||
| 		struct membuffer mb = {}; | ||||
| 		 | ||||
| 		/*
 | ||||
| 		 * 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) | ||||
| 			continue; | ||||
| 
 | ||||
| 		/* make sure the buffer is empty and add the dive */ | ||||
| 		struct dive_site *ds = dive->dive_site; | ||||
| 		mb.len = 0; | ||||
| 		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"); | ||||
| 		} | ||||
| 		membuf = mb_cstring(&mb); | ||||
| 		streamsize = strlen(membuf); | ||||
| 		/*
 | ||||
| 		 * 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(membuf, streamsize, "divelog", NULL, 0); | ||||
| 		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(filename); | ||||
| 			xsltFreeStylesheet(xslt); | ||||
| 			return false; | ||||
| 		} | ||||
| 		free_buffer(&mb); | ||||
| 		 | ||||
| 		transformed = xsltApplyStylesheet(xslt, doc, NULL); | ||||
| 		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(xmlfilename, PATH_MAX, "%d.xml", i + 1); | ||||
| 		s = zip_source_buffer(zip, membuf, streamsize, 1); | ||||
| 		if (s) { | ||||
| 			int64_t ret = zip_add(zip, xmlfilename, 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(filename)), ze, se, zip_strerror(zip)); | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 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); | ||||
| 
 | ||||
| 	// prepare header with filename (of all dives) and pointer to file
 | ||||
| 	args = "form-data; name=\"userfile\"; filename=\"" + filename + "\""; | ||||
| 	part1.setRawHeader("Content-Disposition", args.toLatin1()); | ||||
| 	QFile *f = new QFile(filename); | ||||
| 	if (!f->open(QIODevice::ReadOnly)) { | ||||
| 		qDebug() << "ERROR opening zip file: " << filename; | ||||
| 		return; | ||||
| 	} | ||||
| 	part1.setBodyDevice(f); | ||||
| 	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
 | ||||
| 	connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished())); | ||||
| 	connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, | ||||
| 		SLOT(uploadError(QNetworkReply::NetworkError))); | ||||
| 	connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this, | ||||
| 		SLOT(updateProgress(qint64, qint64))); | ||||
| 	connect(&timeout, SIGNAL(timeout()), this, SLOT(uploadTimeout())); | ||||
| 
 | ||||
| 	timeout.start(30000); // 30s
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void uploadDiveLogsDE::updateProgress(qint64 current, qint64 total) | ||||
| { | ||||
| 	if (!reply) | ||||
| 		return; | ||||
| 	if (total <= 0 || current <= 0) | ||||
| 		return; | ||||
| 
 | ||||
| 	// Calculate percentage
 | ||||
| 	// And signal whoever wants to know
 | ||||
| 	qreal percentage = (float)current / (float)total; | ||||
| 	emit uploadProgress(percentage); | ||||
| 
 | ||||
| 	// reset the timer: 30 seconds after we last got any data
 | ||||
| 	timeout.start(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void uploadDiveLogsDE::uploadFinished() | ||||
| { | ||||
| 	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; | ||||
| 	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; | ||||
| 				} | ||||
| 				err = tr("Upload successful"); | ||||
| 				emit uploadFinish(true, err); | ||||
| 				return; | ||||
| 			} | ||||
| 			err = tr("Login failed"); | ||||
| 			report_error(err.toUtf8()); | ||||
| 			emit uploadFinish(false, err); | ||||
| 			timeout.stop(); | ||||
| 			return; | ||||
| 		} | ||||
| 		err = tr("Cannot parse response"); | ||||
| 		report_error(tr("Cannot parse response").toUtf8()); | ||||
| 		emit uploadFinish(false, err); | ||||
| 		timeout.stop(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void uploadDiveLogsDE::uploadTimeout() | ||||
| { | ||||
| 	QString err(tr("divelogs.de not responding")); | ||||
| 	report_error(err.toUtf8()); | ||||
| 	emit uploadFinish(false, err); | ||||
| 	timeout.stop(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void uploadDiveLogsDE::uploadError(QNetworkReply::NetworkError error) | ||||
| { | ||||
| 	QString err(tr("network error %1").arg(error)); | ||||
| 	report_error(err.toUtf8()); | ||||
| 	emit uploadFinish(false, err); | ||||
| 	timeout.stop(); | ||||
| } | ||||
							
								
								
									
										36
									
								
								core/uploadDiveLogsDE.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								core/uploadDiveLogsDE.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| #ifndef UPLOADDIVELOGSDE_H | ||||
| #define UPLOADDIVELOGSDE_H | ||||
| #include <QNetworkReply> | ||||
| #include <QHttpMultiPart> | ||||
| #include <QTimer> | ||||
| 
 | ||||
| 
 | ||||
| class uploadDiveLogsDE : public QObject { | ||||
| 	Q_OBJECT | ||||
| 	 | ||||
| public: | ||||
| 	static uploadDiveLogsDE *instance(); | ||||
| 	void doUpload(bool selected, const QString &userid, const QString &password); | ||||
| 
 | ||||
| private slots: | ||||
| 	void updateProgress(qint64 current, qint64 total); | ||||
| 	void uploadFinished(); | ||||
| 	void uploadTimeout(); | ||||
| 	void uploadError(QNetworkReply::NetworkError error); | ||||
| 
 | ||||
| signals: | ||||
| 	void uploadFinish(bool success, const QString &text); | ||||
| 	void uploadProgress(qreal percentage); | ||||
| 	 | ||||
| private: | ||||
| 	uploadDiveLogsDE(); | ||||
| 
 | ||||
| 	bool prepareDives(bool selected, const QString &filename); | ||||
| 	void uploadDives(const QString &filename, const QString &userid, const QString &password); | ||||
| 
 | ||||
| 	QNetworkReply *reply; | ||||
| 	QHttpMultiPart *multipart; | ||||
| 	QTimer timeout; | ||||
| }; | ||||
| #endif // UPLOADDIVELOGSDE_H
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue