diff options
Diffstat (limited to 'subsurface-core')
118 files changed, 0 insertions, 42338 deletions
diff --git a/subsurface-core/CMakeLists.txt b/subsurface-core/CMakeLists.txt deleted file mode 100644 index d9b1d3421..000000000 --- a/subsurface-core/CMakeLists.txt +++ /dev/null @@ -1,98 +0,0 @@ -set(PLATFORM_SRC unknown_platform.c) -message(STATUS "system name ${CMAKE_SYSTEM_NAME}") -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - if(ANDROID) - set(PLATFORM_SRC android.cpp) - else() - set(PLATFORM_SRC linux.c) - endif() -elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") - set(PLATFORM_SRC android.cpp) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(PLATFORM_SRC macos.c) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") - set(PLATFORM_SRC windows.c) -endif() - -if(FTDISUPPORT) - set(SERIAL_FTDI serial_ftdi.c) -endif() - -if(BTSUPPORT) - add_definitions(-DBT_SUPPORT) - set(BT_SRC_FILES desktop-widgets/btdeviceselectiondialog.cpp) - set(BT_CORE_SRC_FILES qtserialbluetooth.cpp) -endif() - -# compile the core library, in C. -set(SUBSURFACE_CORE_LIB_SRCS - cochran.c - datatrak.c - deco.c - device.c - dive.c - divesite.c - divesite.cpp - divelist.c - equipment.c - file.c - gas-model.c - git-access.c - libdivecomputer.c - liquivision.c - load-git.c - membuffer.c - ostctools.c - parse-xml.c - planner.c - profile.c - gaspressures.c - worldmap-save.c - save-git.c - save-xml.c - save-html.c - sha1.c - statistics.c - strtod.c - subsurfacestartup.c - time.c - uemis.c - uemis-downloader.c - version.c - # gettextfrommoc should be added because we are using it on the c-code. - gettextfromc.cpp - # dirk ported some core functionality to c++. - qthelper.cpp - divecomputer.cpp - exif.cpp - subsurfacesysinfo.cpp - devicedetails.cpp - configuredivecomputer.cpp - configuredivecomputerthreads.cpp - divesitehelpers.cpp - taxonomy.c - checkcloudconnection.cpp - windowtitleupdate.cpp - divelogexportlogic.cpp - qt-init.cpp - qtserialbluetooth.cpp - metrics.cpp - color.cpp - pluginmanager.cpp - imagedownloader.cpp - isocialnetworkintegration.cpp - gpslocation.cpp - cloudstorage.cpp - - #Subsurface Qt have the Subsurface structs QObjectified for easy access via QML. - subsurface-qt/DiveObjectHelper.cpp - subsurface-qt/SettingsObjectWrapper.cpp - ${SERIAL_FTDI} - ${PLATFORM_SRC} - ${BT_CORE_SRC_FILES} -) -source_group("Subsurface Core" FILES ${SUBSURFACE_CORE_LIB_SRCS}) - -add_library(subsurface_corelib STATIC ${SUBSURFACE_CORE_LIB_SRCS} ) -target_link_libraries(subsurface_corelib ${QT_LIBRARIES}) - diff --git a/subsurface-core/android.cpp b/subsurface-core/android.cpp deleted file mode 100644 index 3631b07a1..000000000 --- a/subsurface-core/android.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/* implements Android specific functions */ -#include "dive.h" -#include "display.h" -#include <string.h> -#include <sys/types.h> -#include <dirent.h> -#include <fcntl.h> -#include <libusb.h> -#include <errno.h> - -#include <QtAndroidExtras/QtAndroidExtras> -#include <QtAndroidExtras/QAndroidJniObject> -#include <QtAndroid> - -#define FTDI_VID 0x0403 -#define USB_SERVICE "usb" - -extern "C" { - -const char android_system_divelist_default_font[] = "Roboto"; -const char *system_divelist_default_font = android_system_divelist_default_font; -double system_divelist_default_font_size = -1; - -int get_usb_fd(uint16_t idVendor, uint16_t idProduct); -void subsurface_OS_pref_setup(void) -{ - // Abusing this function to get a decent place where we can wire in - // our open callback into libusb -#ifdef libusb_android_open_callback_func - libusb_set_android_open_callback(get_usb_fd); -#elif __ANDROID__ -#error we need libusb_android_open_callback -#endif -} - -bool subsurface_ignore_font(const char *font) -{ - // there are no old default fonts that we would want to ignore - return false; -} - -void subsurface_user_info(struct user_info *user) -{ /* Encourage use of at least libgit2-0.20 */ } - -static const char *system_default_path_append(const char *append) -{ - // Qt appears to find a working path for us - let's just go with that - QString path = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first(); - - if (append) - path += QString("/%1").arg(append); - - return strdup(path.toUtf8().data()); -} - -const char *system_default_directory(void) -{ - static const char *path = NULL; - if (!path) - path = system_default_path_append(NULL); - return path; -} - -const char *system_default_filename(void) -{ - static const char *filename = "subsurface.xml"; - static const char *path = NULL; - if (!path) - path = system_default_path_append(filename); - return path; -} - -int enumerate_devices(device_callback_t callback, void *userdata, int dc_type) -{ - /* FIXME: we need to enumerate in some other way on android */ - /* qtserialport maybee? */ - return -1; -} - -/** - * Get the file descriptor of first available matching device attached to usb in android. - * - * returns a fd to the device, or -1 and errno is set. - */ -int get_usb_fd(uint16_t idVendor, uint16_t idProduct) -{ - int i; - jint fd, vendorid, productid; - QAndroidJniObject usbName, usbDevice; - - // Get the current main activity of the application. - QAndroidJniObject activity = QtAndroid::androidActivity(); - - QAndroidJniObject usb_service = QAndroidJniObject::fromString(USB_SERVICE); - - // Get UsbManager from activity - QAndroidJniObject usbManager = activity.callObjectMethod("getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", usb_service.object()); - - // Get a HashMap<Name, UsbDevice> of all USB devices attached to Android - QAndroidJniObject deviceMap = usbManager.callObjectMethod("getDeviceList", "()Ljava/util/HashMap;"); - jint num_devices = deviceMap.callMethod<jint>("size", "()I"); - if (num_devices == 0) { - // No USB device is attached. - return -1; - } - - // Iterate over all the devices and find the first available FTDI device. - QAndroidJniObject keySet = deviceMap.callObjectMethod("keySet", "()Ljava/util/Set;"); - QAndroidJniObject iterator = keySet.callObjectMethod("iterator", "()Ljava/util/Iterator;"); - - for (i = 0; i < num_devices; i++) { - usbName = iterator.callObjectMethod("next", "()Ljava/lang/Object;"); - usbDevice = deviceMap.callObjectMethod ("get", "(Ljava/lang/Object;)Ljava/lang/Object;", usbName.object()); - vendorid = usbDevice.callMethod<jint>("getVendorId", "()I"); - productid = usbDevice.callMethod<jint>("getProductId", "()I"); - if(vendorid == idVendor && productid == idProduct) // Found the requested device - break; - } - if (i == num_devices) { - // No device found. - errno = ENOENT; - return -1; - } - - jboolean hasPermission = usbManager.callMethod<jboolean>("hasPermission", "(Landroid/hardware/usb/UsbDevice;)Z", usbDevice.object()); - if (!hasPermission) { - // You do not have permission to use the usbDevice. - // Please remove and reinsert the USB device. - // Could also give an dialogbox asking for permission. - errno = EPERM; - return -1; - } - - // An device is present and we also have permission to use the device. - // Open the device and get its file descriptor. - QAndroidJniObject usbDeviceConnection = usbManager.callObjectMethod("openDevice", "(Landroid/hardware/usb/UsbDevice;)Landroid/hardware/usb/UsbDeviceConnection;", usbDevice.object()); - if (usbDeviceConnection.object() == NULL) { - // Some error occurred while opening the device. Exit. - errno = EINVAL; - return -1; - } - - // Finally get the required file descriptor. - fd = usbDeviceConnection.callMethod<jint>("getFileDescriptor", "()I"); - if (fd == -1) { - // The device is not opened. Some error. - errno = ENODEV; - return -1; - } - return fd; -} - -/* NOP wrappers to comform with windows.c */ -int subsurface_rename(const char *path, const char *newpath) -{ - return rename(path, newpath); -} - -int subsurface_open(const char *path, int oflags, mode_t mode) -{ - return open(path, oflags, mode); -} - -FILE *subsurface_fopen(const char *path, const char *mode) -{ - return fopen(path, mode); -} - -void *subsurface_opendir(const char *path) -{ - return (void *)opendir(path); -} - -int subsurface_access(const char *path, int mode) -{ - return access(path, mode); -} - -struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp) -{ - return zip_open(path, flags, errorp); -} - -int subsurface_zip_close(struct zip *zip) -{ - return zip_close(zip); -} - -/* win32 console */ -void subsurface_console_init(bool dedicated) -{ - /* NOP */ -} - -void subsurface_console_exit(void) -{ - /* NOP */ -} -} diff --git a/subsurface-core/checkcloudconnection.cpp b/subsurface-core/checkcloudconnection.cpp deleted file mode 100644 index f29d971ba..000000000 --- a/subsurface-core/checkcloudconnection.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include <QObject> -#include <QTimer> -#include <QNetworkAccessManager> -#include <QNetworkReply> -#include <QEventLoop> - -#include "pref.h" -#include "helpers.h" -#include "git-access.h" - -#include "checkcloudconnection.h" - - -CheckCloudConnection::CheckCloudConnection(QObject *parent) : - QObject(parent), - reply(0) -{ - -} - -#define TEAPOT "/make-latte?number-of-shots=3" -#define HTTP_I_AM_A_TEAPOT 418 -#define MILK "Linus does not like non-fat milk" -bool CheckCloudConnection::checkServer() -{ - if (verbose) - fprintf(stderr, "Checking cloud connection...\n"); - - QTimer timer; - timer.setSingleShot(true); - QEventLoop loop; - QNetworkRequest request; - request.setRawHeader("Accept", "text/plain"); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Client-Id", getUUID().toUtf8()); - request.setUrl(QString(prefs.cloud_base_url) + TEAPOT); - QNetworkAccessManager *mgr = new QNetworkAccessManager(); - reply = mgr->get(request); - connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::sslErrors, this, &CheckCloudConnection::sslErrors); - for (int seconds = 1; seconds <= 5; seconds++) { - timer.start(1000); // wait five seconds - loop.exec(); - if (timer.isActive()) { - // didn't time out, did we get the right response? - timer.stop(); - if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == HTTP_I_AM_A_TEAPOT && - reply->readAll() == QByteArray(MILK)) { - reply->deleteLater(); - mgr->deleteLater(); - if (verbose > 1) - qWarning() << "Cloud storage: successfully checked connection to cloud server"; - git_storage_update_progress(last_git_storage_update_val + 1, "successfully checked cloud connection"); - return true; - } - } else if (seconds < 5) { - git_storage_update_progress(last_git_storage_update_val + 1, "waited 1 sec for cloud connection"); - } else { - disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - reply->abort(); - } - } - git_storage_update_progress(last_git_storage_update_val + 1, "cloud connection failed"); - if (verbose) - qDebug() << "connection test to cloud server failed" << - reply->error() << reply->errorString() << - reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << - reply->readAll(); - reply->deleteLater(); - mgr->deleteLater(); - if (verbose) - qWarning() << "Cloud storage: unable to connect to cloud server"; - return false; -} - -void CheckCloudConnection::sslErrors(QList<QSslError> errorList) -{ - if (verbose) { - qDebug() << "Received error response trying to set up https connection with cloud storage backend:"; - Q_FOREACH (QSslError err, errorList) { - qDebug() << err.errorString(); - } - } - QSslConfiguration conf = reply->sslConfiguration(); - QSslCertificate cert = conf.peerCertificate(); - QByteArray hexDigest = cert.digest().toHex(); - if (reply->url().toString().contains(prefs.cloud_base_url) && - hexDigest == "13ff44c62996cfa5cd69d6810675490e") { - if (verbose) - qDebug() << "Overriding SSL check as I recognize the certificate digest" << hexDigest; - reply->ignoreSslErrors(); - } else { - if (verbose) - qDebug() << "got invalid SSL certificate with hex digest" << hexDigest; - } -} - -// helper to be used from C code -extern "C" bool canReachCloudServer() -{ - if (verbose) - qWarning() << "Cloud storage: checking connection to cloud server"; - CheckCloudConnection *checker = new CheckCloudConnection; - return checker->checkServer(); -} diff --git a/subsurface-core/checkcloudconnection.h b/subsurface-core/checkcloudconnection.h deleted file mode 100644 index 58a412797..000000000 --- a/subsurface-core/checkcloudconnection.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef CHECKCLOUDCONNECTION_H -#define CHECKCLOUDCONNECTION_H - -#include <QObject> -#include <QNetworkReply> -#include <QSsl> - -#include "checkcloudconnection.h" - -class CheckCloudConnection : public QObject { - Q_OBJECT -public: - CheckCloudConnection(QObject *parent = 0); - bool checkServer(); -private: - QNetworkReply *reply; -private -slots: - void sslErrors(QList<QSslError> errorList); -}; - -#endif // CHECKCLOUDCONNECTION_H diff --git a/subsurface-core/cloudstorage.cpp b/subsurface-core/cloudstorage.cpp deleted file mode 100644 index 575191891..000000000 --- a/subsurface-core/cloudstorage.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include "cloudstorage.h" -#include "pref.h" -#include "dive.h" -#include "helpers.h" - -#include <QApplication> - -CloudStorageAuthenticate::CloudStorageAuthenticate(QObject *parent) : - QObject(parent), - reply(NULL) -{ - userAgent = getUserAgent(); -} - -#define CLOUDURL QString(prefs.cloud_base_url) -#define CLOUDBACKENDSTORAGE CLOUDURL + "/storage" -#define CLOUDBACKENDVERIFY CLOUDURL + "/verify" -#define CLOUDBACKENDUPDATE CLOUDURL + "/update" - -QNetworkReply* CloudStorageAuthenticate::backend(const QString& email,const QString& password,const QString& pin,const QString& newpasswd) -{ - QString payload(email + QChar(' ') + password); - QUrl requestUrl; - if (pin.isEmpty() && newpasswd.isEmpty()) { - requestUrl = QUrl(CLOUDBACKENDSTORAGE); - } else if (!newpasswd.isEmpty()) { - requestUrl = QUrl(CLOUDBACKENDUPDATE); - payload += QChar(' ') + newpasswd; - } else { - requestUrl = QUrl(CLOUDBACKENDVERIFY); - payload += QChar(' ') + pin; - } - QNetworkRequest *request = new QNetworkRequest(requestUrl); - request->setRawHeader("Accept", "text/xml, text/plain"); - request->setRawHeader("User-Agent", userAgent.toUtf8()); - request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); - reply = manager()->post(*request, qPrintable(payload)); - connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished())); - connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrors(QList<QSslError>))); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, - SLOT(uploadError(QNetworkReply::NetworkError))); - return reply; -} - -void CloudStorageAuthenticate::uploadFinished() -{ - static QString myLastError; - - QString cloudAuthReply(reply->readAll()); - qDebug() << "Completed connection with cloud storage backend, response" << cloudAuthReply; - if (cloudAuthReply == QLatin1String("[VERIFIED]") || cloudAuthReply == QLatin1String("[OK]")) { - prefs.cloud_verification_status = CS_VERIFIED; - /* TODO: Move this to a correct place - NotificationWidget *nw = MainWindow::instance()->getNotificationWidget(); - if (nw->getNotificationText() == myLastError) - nw->hideNotification(); - */ - myLastError.clear(); - } else if (cloudAuthReply == QLatin1String("[VERIFY]")) { - prefs.cloud_verification_status = CS_NEED_TO_VERIFY; - } else if (cloudAuthReply == QLatin1String("[PASSWDCHANGED]")) { - free(prefs.cloud_storage_password); - prefs.cloud_storage_password = prefs.cloud_storage_newpassword; - prefs.cloud_storage_newpassword = NULL; - emit passwordChangeSuccessful(); - return; - } else { - prefs.cloud_verification_status = CS_INCORRECT_USER_PASSWD; - myLastError = cloudAuthReply; - report_error("%s", qPrintable(cloudAuthReply)); - /* TODO: Emit a signal with the error - MainWindow::instance()->getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); - */ - } - emit finishedAuthenticate(); -} - -void CloudStorageAuthenticate::uploadError(QNetworkReply::NetworkError) -{ - qDebug() << "Received error response from cloud storage backend:" << reply->errorString(); -} - -void CloudStorageAuthenticate::sslErrors(QList<QSslError> errorList) -{ - if (verbose) { - qDebug() << "Received error response trying to set up https connection with cloud storage backend:"; - Q_FOREACH (QSslError err, errorList) { - qDebug() << err.errorString(); - } - } - QSslConfiguration conf = reply->sslConfiguration(); - QSslCertificate cert = conf.peerCertificate(); - QByteArray hexDigest = cert.digest().toHex(); - if (reply->url().toString().contains(prefs.cloud_base_url) && - hexDigest == "13ff44c62996cfa5cd69d6810675490e") { - if (verbose) - qDebug() << "Overriding SSL check as I recognize the certificate digest" << hexDigest; - reply->ignoreSslErrors(); - } else { - if (verbose) - qDebug() << "got invalid SSL certificate with hex digest" << hexDigest; - } -} - -QNetworkAccessManager *manager() -{ - static QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); - return manager; -} diff --git a/subsurface-core/cloudstorage.h b/subsurface-core/cloudstorage.h deleted file mode 100644 index 6addb739d..000000000 --- a/subsurface-core/cloudstorage.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef CLOUD_STORAGE_H -#define CLOUD_STORAGE_H - -#include <QObject> -#include <QNetworkReply> - -class CloudStorageAuthenticate : public QObject { - Q_OBJECT -public: - QNetworkReply* backend(const QString& email,const QString& password,const QString& pin = QString(),const QString& newpasswd = QString()); - explicit CloudStorageAuthenticate(QObject *parent); -signals: - void finishedAuthenticate(); - void passwordChangeSuccessful(); -private -slots: - void uploadError(QNetworkReply::NetworkError error); - void sslErrors(QList<QSslError> errorList); - void uploadFinished(); -private: - QNetworkReply *reply; - QString userAgent; - bool verbose; -}; - -QNetworkAccessManager *manager(); -#endif
\ No newline at end of file diff --git a/subsurface-core/cochran.c b/subsurface-core/cochran.c deleted file mode 100644 index b42ed8233..000000000 --- a/subsurface-core/cochran.c +++ /dev/null @@ -1,809 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> - -#include "dive.h" -#include "file.h" -#include "units.h" -#include "gettext.h" -#include "cochran.h" -#include "divelist.h" - -#include <libdivecomputer/parser.h> - -#define POUND 0.45359237 -#define FEET 0.3048 -#define INCH 0.0254 -#define GRAVITY 9.80665 -#define ATM 101325.0 -#define BAR 100000.0 -#define FSW (ATM / 33.0) -#define MSW (BAR / 10.0) -#define PSI ((POUND * GRAVITY) / (INCH * INCH)) - -// Some say 0x4a14 and 0x4b14 are the right number for this offset -// This works with CAN files from Analyst 4.01v and computers -// such as Commander, Gemini, EMC-16, and EMC-20H -#define LOG_ENTRY_OFFSET 0x4914 - -enum cochran_type { - TYPE_GEMINI, - TYPE_COMMANDER, - TYPE_EMC -}; - -struct config { - enum cochran_type type; - unsigned int logbook_size; - unsigned int sample_size; -} config; - - -// Convert 4 bytes into an INT -#define array_uint16_le(p) ((unsigned int) (p)[0] \ - + ((p)[1]<<8) ) -#define array_uint32_le(p) ((unsigned int) (p)[0] \ - + ((p)[1]<<8) + ((p)[2]<<16) \ - + ((p)[3]<<24)) - -/* - * The Cochran file format is designed to be annoying to read. It's roughly: - * - * 0x00000: room for 65534 4-byte words, giving the starting offsets - * of the dives themselves. - * - * 0x3fff8: the size of the file + 1 - * 0x3ffff: 0 (high 32 bits of filesize? Bogus: the offsets into the file - * are 32-bit, so it can't be a large file anyway) - * - * 0x40000: byte 0x46 - * 0x40001: "block 0": 256 byte encryption key - * 0x40101: the random modulus, or length of the key to use - * 0x40102: block 1: Version and date of Analyst and a feature string identifying - * the computer features and the features of the file - * 0x40138: Computer configuration page 1, 512 bytes - * 0x40338: Computer configuration page 2, 512 bytes - * 0x40538: Misc data (tissues) 1500 bytes - * 0x40b14: Ownership data 512 bytes ??? - * - * 0x4171c: Ownership data 512 bytes ??? <copy> - * - * 0x45415: Time stamp 17 bytes - * 0x45426: Computer configuration page 1, 512 bytes <copy> - * 0x45626: Computer configuration page 2, 512 bytes <copy> - * - */ -static unsigned int partial_decode(unsigned int start, unsigned int end, - const unsigned char *decode, unsigned offset, unsigned mod, - const unsigned char *buf, unsigned int size, unsigned char *dst) -{ - unsigned i, sum = 0; - - for (i = start; i < end; i++) { - unsigned char d = decode[offset++]; - if (i >= size) - break; - if (offset == mod) - offset = 0; - d += buf[i]; - if (dst) - dst[i] = d; - sum += d; - } - return sum; -} - -#ifdef COCHRAN_DEBUG - -#define hexchar(n) ("0123456789abcdef"[(n) & 15]) - -static int show_line(unsigned offset, const unsigned char *data, - unsigned size, int show_empty) -{ - unsigned char bits; - int i, off; - char buffer[120]; - - if (size > 16) - size = 16; - - bits = 0; - memset(buffer, ' ', sizeof(buffer)); - off = sprintf(buffer, "%06x ", offset); - for (i = 0; i < size; i++) { - char *hex = buffer + off + 3 * i; - char *asc = buffer + off + 50 + i; - unsigned char byte = data[i]; - - hex[0] = hexchar(byte >> 4); - hex[1] = hexchar(byte); - bits |= byte; - if (byte < 32 || byte > 126) - byte = '.'; - asc[0] = byte; - asc[1] = 0; - } - - if (bits) { - puts(buffer); - return 1; - } - if (show_empty) - puts("..."); - return 0; -} - -static void cochran_debug_write(const unsigned char *data, unsigned size) -{ - return; - - int show = 1, i; - for (i = 0; i < size; i += 16) - show = show_line(i, data + i, size - i, show); -} - -static void cochran_debug_sample(const char *s, unsigned int seconds) -{ - switch (config.type) { - case TYPE_GEMINI: - switch (seconds % 4) { - case 0: - printf("Hex: %02x %02x ", s[0], s[1]); - break; - case 1: - printf("Hex: %02x %02x ", s[0], s[1]); - break; - case 2: - printf("Hex: %02x %02x ", s[0], s[1]); - break; - case 3: - printf("Hex: %02x %02x ", s[0], s[1]); - break; - } - break; - case TYPE_COMMANDER: - switch (seconds % 2) { - case 0: - printf("Hex: %02x %02x ", s[0], s[1]); - break; - case 1: - printf("Hex: %02x %02x ", s[0], s[1]); - break; - } - break; - case TYPE_EMC: - switch (seconds % 2) { - case 0: - printf("Hex: %02x %02x %02x ", s[0], s[1], s[2]); - break; - case 1: - printf("Hex: %02x %02x %02x ", s[0], s[1], s[2]); - break; - } - break; - } - - printf ("%02dh %02dm %02ds: Depth: %-5.2f, ", seconds / 3660, - (seconds % 3660) / 60, seconds % 60, depth); -} - -#endif // COCHRAN_DEBUG - -static void cochran_parse_header(const unsigned char *decode, unsigned mod, - const unsigned char *in, unsigned size) -{ - unsigned char *buf = malloc(size); - - /* Do the "null decode" using a one-byte decode array of '\0' */ - /* Copies in plaintext, will be overwritten later */ - partial_decode(0, 0x0102, (const unsigned char *)"", 0, 1, in, size, buf); - - /* - * The header scrambling is different form the dive - * scrambling. Oh yay! - */ - partial_decode(0x0102, 0x010e, decode, 0, mod, in, size, buf); - partial_decode(0x010e, 0x0b14, decode, 0, mod, in, size, buf); - partial_decode(0x0b14, 0x1b14, decode, 0, mod, in, size, buf); - partial_decode(0x1b14, 0x2b14, decode, 0, mod, in, size, buf); - partial_decode(0x2b14, 0x3b14, decode, 0, mod, in, size, buf); - partial_decode(0x3b14, 0x5414, decode, 0, mod, in, size, buf); - partial_decode(0x5414, size, decode, 0, mod, in, size, buf); - - // Detect log type - switch (buf[0x133]) { - case '2': // Cochran Commander, version II log format - config.logbook_size = 256; - if (buf[0x132] == 0x10) { - config.type = TYPE_GEMINI; - config.sample_size = 2; // Gemini with tank PSI samples - } else { - config.type = TYPE_COMMANDER; - config.sample_size = 2; // Commander - } - break; - case '3': // Cochran EMC, version III log format - config.type = TYPE_EMC; - config.logbook_size = 512; - config.sample_size = 3; - break; - default: - printf ("Unknown log format v%c\n", buf[0x137]); - free(buf); - exit(1); - break; - } - -#ifdef COCHRAN_DEBUG - puts("Header\n======\n\n"); - cochran_debug_write(buf, size); -#endif - - free(buf); -} - -/* -* Bytes expected after a pre-dive event code -*/ -static int cochran_predive_event_bytes(unsigned char code) -{ - int x = 0; - int gem_event_bytes[15][2] = {{0x00, 10}, {0x02, 17}, {0x08, 18}, - {0x09, 18}, {0x0c, 18}, {0x0d, 18}, - {0x0e, 18}, - {-1, 0}}; - int cmdr_event_bytes[15][2] = {{0x00, 16}, {0x01, 20}, {0x02, 17}, - {0x03, 16}, {0x06, 18}, {0x07, 18}, - {0x08, 18}, {0x09, 18}, {0x0a, 18}, - {0x0b, 20}, {0x0c, 18}, {0x0d, 18}, - {0x0e, 18}, {0x10, 20}, - {-1, 0}}; - int emc_event_bytes[15][2] = {{0x00, 18}, {0x01, 22}, {0x02, 19}, - {0x03, 18}, {0x06, 20}, {0x07, 20}, - {0x0a, 20}, {0x0b, 20}, {0x0f, 18}, - {0x10, 20}, - {-1, 0}}; - - switch (config.type) { - case TYPE_GEMINI: - while (gem_event_bytes[x][0] != code && gem_event_bytes[x][0] != -1) - x++; - return gem_event_bytes[x][1]; - break; - case TYPE_COMMANDER: - while (cmdr_event_bytes[x][0] != code && cmdr_event_bytes[x][0] != -1) - x++; - return cmdr_event_bytes[x][1]; - break; - case TYPE_EMC: - while (emc_event_bytes[x][0] != code && emc_event_bytes[x][0] != -1) - x++; - return emc_event_bytes[x][1]; - break; - } - - return 0; -} - -int cochran_dive_event_bytes(unsigned char event) -{ - return (event == 0xAD || event == 0xAB) ? 4 : 0; -} - -static void cochran_dive_event(struct divecomputer *dc, const unsigned char *s, - unsigned int seconds, unsigned int *in_deco, - unsigned int *deco_ceiling, unsigned int *deco_time) -{ - switch (s[0]) { - case 0xC5: // Deco obligation begins - *in_deco = 1; - add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP, - SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "deco stop")); - break; - case 0xDB: // Deco obligation ends - *in_deco = 0; - add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP, - SAMPLE_FLAGS_END, 0, - QT_TRANSLATE_NOOP("gettextFromC", "deco stop")); - break; - case 0xAD: // Raise deco ceiling 10 ft - *deco_ceiling -= 10; // ft - *deco_time = (array_uint16_le(s + 3) + 1) * 60; - break; - case 0xAB: // Lower deco ceiling 10 ft - *deco_ceiling += 10; // ft - *deco_time = (array_uint16_le(s + 3) + 1) * 60; - break; - case 0xA8: // Entered Post Dive interval mode (surfaced) - break; - case 0xA9: // Exited PDI mode (re-submierged) - break; - case 0xBD: // Switched to normal PO2 setting - break; - case 0xC0: // Switched to FO2 21% mode (generally upon surface) - break; - case 0xC1: // "Ascent rate alarm - add_event(dc, seconds, SAMPLE_EVENT_ASCENT, - SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "ascent")); - break; - case 0xC2: // Low battery warning -#ifdef SAMPLE_EVENT_BATTERY - add_event(dc, seconds, SAMPLE_EVENT_BATTERY, - SAMPLE_FLAGS_NONE, 0, - QT_TRANSLATE_NOOP("gettextFromC", "battery")); -#endif - break; - case 0xC3: // CNS warning - add_event(dc, seconds, SAMPLE_EVENT_OLF, - SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "OLF")); - break; - case 0xC4: // Depth alarm begin - add_event(dc, seconds, SAMPLE_EVENT_MAXDEPTH, - SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "maxdepth")); - break; - case 0xC8: // PPO2 alarm begin - add_event(dc, seconds, SAMPLE_EVENT_PO2, - SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "pOâ‚‚")); - break; - case 0xCC: // Low cylinder 1 pressure"; - break; - case 0xCD: // Switch to deco blend setting - add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE, - SAMPLE_FLAGS_NONE, 0, - QT_TRANSLATE_NOOP("gettextFromC", "gaschange")); - break; - case 0xCE: // NDL alarm begin - add_event(dc, seconds, SAMPLE_EVENT_RBT, - SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "rbt")); - break; - case 0xD0: // Breathing rate alarm begin - break; - case 0xD3: // Low gas 1 flow rate alarm begin"; - break; - case 0xD6: // Ceiling alarm begin - add_event(dc, seconds, SAMPLE_EVENT_CEILING, - SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "ceiling")); - break; - case 0xD8: // End decompression mode - *in_deco = 0; - add_event(dc, seconds, SAMPLE_EVENT_DECOSTOP, - SAMPLE_FLAGS_END, 0, - QT_TRANSLATE_NOOP("gettextFromC", "deco stop")); - break; - case 0xE1: // Ascent alarm end - add_event(dc, seconds, SAMPLE_EVENT_ASCENT, - SAMPLE_FLAGS_END, 0, - QT_TRANSLATE_NOOP("gettextFromC", "ascent")); - break; - case 0xE2: // Low transmitter battery alarm - add_event(dc, seconds, SAMPLE_EVENT_TRANSMITTER, - SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "transmitter")); - break; - case 0xE3: // Switch to FO2 mode - break; - case 0xE5: // Switched to PO2 mode - break; - case 0xE8: // PO2 too low alarm - add_event(dc, seconds, SAMPLE_EVENT_PO2, - SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "pOâ‚‚")); - break; - case 0xEE: // NDL alarm end - add_event(dc, seconds, SAMPLE_EVENT_RBT, - SAMPLE_FLAGS_END, 0, - QT_TRANSLATE_NOOP("gettextFromC", "rbt")); - break; - case 0xEF: // Switch to blend 2 - add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE, - SAMPLE_FLAGS_NONE, 0, - QT_TRANSLATE_NOOP("gettextFromC", "gaschange")); - break; - case 0xF0: // Breathing rate alarm end - break; - case 0xF3: // Switch to blend 1 (often at dive start) - add_event(dc, seconds, SAMPLE_EVENT_GASCHANGE, - SAMPLE_FLAGS_NONE, 0, - QT_TRANSLATE_NOOP("gettextFromC", "gaschange")); - break; - case 0xF6: // Ceiling alarm end - add_event(dc, seconds, SAMPLE_EVENT_CEILING, - SAMPLE_FLAGS_END, 0, - QT_TRANSLATE_NOOP("gettextFromC", "ceiling")); - break; - default: - break; - } -} - -/* -* Parse sample data, extract events and build a dive -*/ -static void cochran_parse_samples(struct dive *dive, const unsigned char *log, - const unsigned char *samples, unsigned int size, - unsigned int *duration, double *max_depth, - double *avg_depth, double *min_temp) -{ - const unsigned char *s; - unsigned int offset = 0, seconds = 0; - double depth = 0, temp = 0, depth_sample = 0, psi = 0, sgc_rate = 0; - int ascent_rate = 0; - unsigned int ndl = 0; - unsigned int in_deco = 0, deco_ceiling = 0, deco_time = 0; - - struct divecomputer *dc = &dive->dc; - struct sample *sample; - - // Initialize stat variables - *max_depth = 0, *avg_depth = 0, *min_temp = 0xFF; - - // Get starting depth and temp (tank PSI???) - switch (config.type) { - case TYPE_GEMINI: - depth = (float) (log[CMD_START_DEPTH] - + log[CMD_START_DEPTH + 1] * 256) / 4; - temp = log[CMD_START_TEMP]; - psi = log[CMD_START_PSI] + log[CMD_START_PSI + 1] * 256; - sgc_rate = (float)(log[CMD_START_SGC] - + log[CMD_START_SGC + 1] * 256) / 2; - break; - case TYPE_COMMANDER: - depth = (float) (log[CMD_START_DEPTH] - + log[CMD_START_DEPTH + 1] * 256) / 4; - temp = log[CMD_START_TEMP]; - break; - - case TYPE_EMC: - depth = (float) log [EMC_START_DEPTH] / 256 - + log[EMC_START_DEPTH + 1]; - temp = log[EMC_START_TEMP]; - break; - } - - // Skip past pre-dive events - unsigned int x = 0; - if (samples[x] != 0x40) { - unsigned int c; - while ((samples[x] & 0x80) == 0 && samples[x] != 0x40 && x < size) { - c = cochran_predive_event_bytes(samples[x]) + 1; -#ifdef COCHRAN_DEBUG - printf("Predive event: ", samples[x]); - for (int y = 0; y < c; y++) printf("%02x ", samples[x + y]); - putchar('\n'); -#endif - x += c; - } - } - - // Now process samples - offset = x; - while (offset < size) { - s = samples + offset; - - // Start with an empty sample - sample = prepare_sample(dc); - sample->time.seconds = seconds; - - // Check for event - if (s[0] & 0x80) { - cochran_dive_event(dc, s, seconds, &in_deco, &deco_ceiling, &deco_time); - offset += cochran_dive_event_bytes(s[0]) + 1; - continue; - } - - // Depth is in every sample - depth_sample = (float)(s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); - depth += depth_sample; - -#ifdef COCHRAN_DEBUG - cochran_debug_sample(s, seconds); -#endif - - switch (config.type) { - case TYPE_COMMANDER: - switch (seconds % 2) { - case 0: // Ascent rate - ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1); - break; - case 1: // Temperature - temp = s[1] / 2 + 20; - break; - } - break; - case TYPE_GEMINI: - // Gemini with tank pressure and SAC rate. - switch (seconds % 4) { - case 0: // Ascent rate - ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1); - break; - case 2: // PSI change - psi -= (float)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 4; - break; - case 1: // SGC rate - sgc_rate -= (float)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 2; - break; - case 3: // Temperature - temp = (float)s[1] / 2 + 20; - break; - } - break; - case TYPE_EMC: - switch (seconds % 2) { - case 0: // Ascent rate - ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1); - break; - case 1: // Temperature - temp = (float)s[1] / 2 + 20; - break; - } - // Get NDL and deco information - switch (seconds % 24) { - case 20: - if (in_deco) { - // Fist stop time - //first_deco_time = (s[2] + s[5] * 256 + 1) * 60; // seconds - ndl = 0; - } else { - // NDL - ndl = (s[2] + s[5] * 256 + 1) * 60; // seconds - deco_time = 0; - } - break; - case 22: - if (in_deco) { - // Total stop time - deco_time = (s[2] + s[5] * 256 + 1) * 60; // seconds - ndl = 0; - } - break; - } - } - - // Track dive stats - if (depth > *max_depth) *max_depth = depth; - if (temp < *min_temp) *min_temp = temp; - *avg_depth = (*avg_depth * seconds + depth) / (seconds + 1); - - sample->depth.mm = depth * FEET * 1000; - sample->ndl.seconds = ndl; - sample->in_deco = in_deco; - sample->stoptime.seconds = deco_time; - sample->stopdepth.mm = deco_ceiling * FEET * 1000; - sample->temperature.mkelvin = C_to_mkelvin((temp - 32) / 1.8); - sample->sensor = 0; - sample->cylinderpressure.mbar = psi * PSI / 100; - - finish_sample(dc); - - offset += config.sample_size; - seconds++; - } - (void)ascent_rate; // mark the variable as unused - - if (seconds > 0) - *duration = seconds - 1; -} - -static void cochran_parse_dive(const unsigned char *decode, unsigned mod, - const unsigned char *in, unsigned size) -{ - unsigned char *buf = malloc(size); - struct dive *dive; - struct divecomputer *dc; - struct tm tm = {0}; - uint32_t csum[5]; - - double max_depth, avg_depth, min_temp; - unsigned int duration = 0, corrupt_dive = 0; - - /* - * The scrambling has odd boundaries. I think the boundaries - * match some data structure size, but I don't know. They were - * discovered the same way we dynamically discover the decode - * size: automatically looking for least random output. - * - * The boundaries are also this confused "off-by-one" thing, - * the same way the file size is off by one. It's as if the - * cochran software forgot to write one byte at the beginning. - */ - partial_decode(0, 0x0fff, decode, 1, mod, in, size, buf); - partial_decode(0x0fff, 0x1fff, decode, 0, mod, in, size, buf); - partial_decode(0x1fff, 0x2fff, decode, 0, mod, in, size, buf); - partial_decode(0x2fff, 0x48ff, decode, 0, mod, in, size, buf); - - /* - * This is not all the descrambling you need - the above are just - * what appears to be the fixed-size blocks. The rest is also - * scrambled, but there seems to be size differences in the data, - * so this just descrambles part of it: - */ - // Decode log entry (512 bytes + random prefix) - partial_decode(0x48ff, 0x4914 + config.logbook_size, decode, - 0, mod, in, size, buf); - - unsigned int sample_size = size - 0x4914 - config.logbook_size; - int g; - - // Decode sample data - partial_decode(0x4914 + config.logbook_size, size, decode, - 0, mod, in, size, buf); - -#ifdef COCHRAN_DEBUG - // Display pre-logbook data - puts("\nPre Logbook Data\n"); - cochran_debug_write(buf, 0x4914); - - // Display log book - puts("\nLogbook Data\n"); - cochran_debug_write(buf + 0x4914, config.logbook_size + 0x400); - - // Display sample data - puts("\nSample Data\n"); -#endif - - dive = alloc_dive(); - dc = &dive->dc; - - unsigned char *log = (buf + 0x4914); - - switch (config.type) { - case TYPE_GEMINI: - case TYPE_COMMANDER: - if (config.type == TYPE_GEMINI) { - dc->model = "Gemini"; - dc->deviceid = buf[0x18c] * 256 + buf[0x18d]; // serial no - fill_default_cylinder(&dive->cylinder[0]); - dive->cylinder[0].gasmix.o2.permille = (log[CMD_O2_PERCENT] / 256 - + log[CMD_O2_PERCENT + 1]) * 10; - dive->cylinder[0].gasmix.he.permille = 0; - } else { - dc->model = "Commander"; - dc->deviceid = array_uint32_le(buf + 0x31e); // serial no - for (g = 0; g < 2; g++) { - fill_default_cylinder(&dive->cylinder[g]); - dive->cylinder[g].gasmix.o2.permille = (log[CMD_O2_PERCENT + g * 2] / 256 - + log[CMD_O2_PERCENT + g * 2 + 1]) * 10; - dive->cylinder[g].gasmix.he.permille = 0; - } - } - - tm.tm_year = log[CMD_YEAR]; - tm.tm_mon = log[CMD_MON] - 1; - tm.tm_mday = log[CMD_DAY]; - tm.tm_hour = log[CMD_HOUR]; - tm.tm_min = log[CMD_MIN]; - tm.tm_sec = log[CMD_SEC]; - tm.tm_isdst = -1; - - dive->when = dc->when = utc_mktime(&tm); - dive->number = log[CMD_NUMBER] + log[CMD_NUMBER + 1] * 256 + 1; - dc->duration.seconds = (log[CMD_BT] + log[CMD_BT + 1] * 256) * 60; - dc->surfacetime.seconds = (log[CMD_SIT] + log[CMD_SIT + 1] * 256) * 60; - dc->maxdepth.mm = (log[CMD_MAX_DEPTH] + - log[CMD_MAX_DEPTH + 1] * 256) / 4 * FEET * 1000; - dc->meandepth.mm = (log[CMD_AVG_DEPTH] + - log[CMD_AVG_DEPTH + 1] * 256) / 4 * FEET * 1000; - dc->watertemp.mkelvin = C_to_mkelvin((log[CMD_MIN_TEMP] / 32) - 1.8); - dc->surface_pressure.mbar = ATM / BAR * pow(1 - 0.0000225577 - * (double) log[CMD_ALTITUDE] * 250 * FEET, 5.25588) * 1000; - dc->salinity = 10000 + 150 * log[CMD_WATER_CONDUCTIVITY]; - - SHA1(log + CMD_NUMBER, 2, (unsigned char *)csum); - dc->diveid = csum[0]; - - if (log[CMD_MAX_DEPTH] == 0xff && log[CMD_MAX_DEPTH + 1] == 0xff) - corrupt_dive = 1; - - break; - case TYPE_EMC: - dc->model = "EMC"; - dc->deviceid = array_uint32_le(buf + 0x31e); // serial no - for (g = 0; g < 4; g++) { - fill_default_cylinder(&dive->cylinder[g]); - dive->cylinder[g].gasmix.o2.permille = - (log[EMC_O2_PERCENT + g * 2] / 256 - + log[EMC_O2_PERCENT + g * 2 + 1]) * 10; - dive->cylinder[g].gasmix.he.permille = - (log[EMC_HE_PERCENT + g * 2] / 256 - + log[EMC_HE_PERCENT + g * 2 + 1]) * 10; - } - - tm.tm_year = log[EMC_YEAR]; - tm.tm_mon = log[EMC_MON] - 1; - tm.tm_mday = log[EMC_DAY]; - tm.tm_hour = log[EMC_HOUR]; - tm.tm_min = log[EMC_MIN]; - tm.tm_sec = log[EMC_SEC]; - tm.tm_isdst = -1; - - dive->when = dc->when = utc_mktime(&tm); - dive->number = log[EMC_NUMBER] + log[EMC_NUMBER + 1] * 256 + 1; - dc->duration.seconds = (log[EMC_BT] + log[EMC_BT + 1] * 256) * 60; - dc->surfacetime.seconds = (log[EMC_SIT] + log[EMC_SIT + 1] * 256) * 60; - dc->maxdepth.mm = (log[EMC_MAX_DEPTH] + - log[EMC_MAX_DEPTH + 1] * 256) / 4 * FEET * 1000; - dc->meandepth.mm = (log[EMC_AVG_DEPTH] + - log[EMC_AVG_DEPTH + 1] * 256) / 4 * FEET * 1000; - dc->watertemp.mkelvin = C_to_mkelvin((log[EMC_MIN_TEMP] - 32) / 1.8); - dc->surface_pressure.mbar = ATM / BAR * pow(1 - 0.0000225577 - * (double) log[EMC_ALTITUDE] * 250 * FEET, 5.25588) * 1000; - dc->salinity = 10000 + 150 * (log[EMC_WATER_CONDUCTIVITY] & 0x3); - - SHA1(log + EMC_NUMBER, 2, (unsigned char *)csum); - dc->diveid = csum[0]; - - if (log[EMC_MAX_DEPTH] == 0xff && log[EMC_MAX_DEPTH + 1] == 0xff) - corrupt_dive = 1; - - break; - } - - cochran_parse_samples(dive, buf + 0x4914, buf + 0x4914 - + config.logbook_size, sample_size, - &duration, &max_depth, &avg_depth, &min_temp); - - // Check for corrupt dive - if (corrupt_dive) { - dc->maxdepth.mm = max_depth * FEET * 1000; - dc->meandepth.mm = avg_depth * FEET * 1000; - dc->watertemp.mkelvin = C_to_mkelvin((min_temp - 32) / 1.8); - dc->duration.seconds = duration; - } - - dive->downloaded = true; - record_dive(dive); - mark_divelist_changed(true); - - free(buf); -} - -int try_to_open_cochran(const char *filename, struct memblock *mem) -{ - (void) filename; - unsigned int i; - unsigned int mod; - unsigned int *offsets, dive1, dive2; - unsigned char *decode = mem->buffer + 0x40001; - - if (mem->size < 0x40000) - return 0; - - offsets = (unsigned int *) mem->buffer; - dive1 = offsets[0]; - dive2 = offsets[1]; - - if (dive1 < 0x40000 || dive2 < dive1 || dive2 > mem->size) - return 0; - - mod = decode[0x100] + 1; - cochran_parse_header(decode, mod, mem->buffer + 0x40000, dive1 - 0x40000); - - // Decode each dive - for (i = 0; i < 65534; i++) { - dive1 = offsets[i]; - dive2 = offsets[i + 1]; - if (dive2 < dive1) - break; - if (dive2 > mem->size) - break; - - cochran_parse_dive(decode, mod, mem->buffer + dive1, - dive2 - dive1); - } - - return 1; // no further processing needed -} diff --git a/subsurface-core/cochran.h b/subsurface-core/cochran.h deleted file mode 100644 index 97d4361c8..000000000 --- a/subsurface-core/cochran.h +++ /dev/null @@ -1,44 +0,0 @@ -// Commander log fields -#define CMD_SEC 1 -#define CMD_MIN 0 -#define CMD_HOUR 3 -#define CMD_DAY 2 -#define CMD_MON 5 -#define CMD_YEAR 4 -#define CME_START_OFFSET 6 // 4 bytes -#define CMD_WATER_CONDUCTIVITY 25 // 1 byte, 0=low, 2=high -#define CMD_START_SGC 42 // 2 bytes -#define CMD_START_TEMP 45 // 1 byte, F -#define CMD_START_DEPTH 56 // 2 bytes, /4=ft -#define CMD_START_PSI 62 -#define CMD_SIT 68 // 2 bytes, minutes -#define CMD_NUMBER 70 // 2 bytes -#define CMD_ALTITUDE 73 // 1 byte, /4=Kilofeet -#define CMD_END_OFFSET 128 // 4 bytes -#define CMD_MIN_TEMP 153 // 1 byte, F -#define CMD_BT 166 // 2 bytes, minutes -#define CMD_MAX_DEPTH 168 // 2 bytes, /4=ft -#define CMD_AVG_DEPTH 170 // 2 bytes, /4=ft -#define CMD_O2_PERCENT 210 // 8 bytes, 4 x 2 byte, /256=% - -// EMC log fields -#define EMC_SEC 0 -#define EMC_MIN 1 -#define EMC_HOUR 2 -#define EMC_DAY 3 -#define EMC_MON 4 -#define EMC_YEAR 5 -#define EMC_START_OFFSET 6 // 4 bytes -#define EMC_WATER_CONDUCTIVITY 24 // 1 byte bits 0:1, 0=low, 2=high -#define EMC_START_DEPTH 42 // 2 byte, /256=ft -#define EMC_START_TEMP 55 // 1 byte, F -#define EMC_SIT 84 // 2 bytes, minutes, LE -#define EMC_NUMBER 86 // 2 bytes -#define EMC_ALTITUDE 89 // 1 byte, /4=Kilofeet -#define EMC_O2_PERCENT 144 // 20 bytes, 10 x 2 bytes, /256=% -#define EMC_HE_PERCENT 164 // 20 bytes, 10 x 2 bytes, /256=% -#define EMC_END_OFFSET 256 // 4 bytes -#define EMC_MIN_TEMP 293 // 1 byte, F -#define EMC_BT 304 // 2 bytes, minutes -#define EMC_MAX_DEPTH 306 // 2 bytes, /4=ft -#define EMC_AVG_DEPTH 310 // 2 bytes, /4=ft diff --git a/subsurface-core/color.cpp b/subsurface-core/color.cpp deleted file mode 100644 index cf6f43916..000000000 --- a/subsurface-core/color.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "color.h" - -QMap<color_indice_t, QVector<QColor> > profile_color; - -void fill_profile_color() -{ -#define COLOR(x, y, z) QVector<QColor>() << x << y << z; - profile_color[SAC_1] = COLOR(FUNGREEN1, BLACK1_LOW_TRANS, FUNGREEN1); - profile_color[SAC_2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1); - profile_color[SAC_3] = COLOR(ATLANTIS1, BLACK1_LOW_TRANS, ATLANTIS1); - profile_color[SAC_4] = COLOR(ATLANTIS2, BLACK1_LOW_TRANS, ATLANTIS2); - profile_color[SAC_5] = COLOR(EARLSGREEN1, BLACK1_LOW_TRANS, EARLSGREEN1); - profile_color[SAC_6] = COLOR(HOKEYPOKEY1, BLACK1_LOW_TRANS, HOKEYPOKEY1); - profile_color[SAC_7] = COLOR(TUSCANY1, BLACK1_LOW_TRANS, TUSCANY1); - profile_color[SAC_8] = COLOR(CINNABAR1, BLACK1_LOW_TRANS, CINNABAR1); - profile_color[SAC_9] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1); - - profile_color[VELO_STABLE] = COLOR(CAMARONE1, BLACK1_LOW_TRANS, CAMARONE1); - profile_color[VELO_SLOW] = COLOR(LIMENADE1, BLACK1_LOW_TRANS, LIMENADE1); - profile_color[VELO_MODERATE] = COLOR(RIOGRANDE1, BLACK1_LOW_TRANS, RIOGRANDE1); - profile_color[VELO_FAST] = COLOR(PIRATEGOLD1, BLACK1_LOW_TRANS, PIRATEGOLD1); - profile_color[VELO_CRAZY] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); - - profile_color[PO2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1); - profile_color[PO2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); - profile_color[PN2] = COLOR(BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS); - profile_color[PN2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); - profile_color[PHE] = COLOR(PEANUT, BLACK1_LOW_TRANS, PEANUT); - profile_color[PHE_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); - profile_color[O2SETPOINT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); - profile_color[CCRSENSOR1] = COLOR(TUNDORA1_MED_TRANS, BLACK1_LOW_TRANS, TUNDORA1_MED_TRANS); - profile_color[CCRSENSOR2] = COLOR(ROYALBLUE2_LOW_TRANS, BLACK1_LOW_TRANS, ROYALBLUE2_LOW_TRANS); - profile_color[CCRSENSOR3] = COLOR(PEANUT, BLACK1_LOW_TRANS, PEANUT); - profile_color[PP_LINES] = COLOR(BLACK1_HIGH_TRANS, BLACK1_LOW_TRANS, BLACK1_HIGH_TRANS); - - profile_color[TEXT_BACKGROUND] = COLOR(CONCRETE1_LOWER_TRANS, WHITE1, CONCRETE1_LOWER_TRANS); - profile_color[ALERT_BG] = COLOR(BROOM1_LOWER_TRANS, BLACK1_LOW_TRANS, BROOM1_LOWER_TRANS); - profile_color[ALERT_FG] = COLOR(BLACK1_LOW_TRANS, WHITE1, BLACK1_LOW_TRANS); - profile_color[EVENTS] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1); - profile_color[SAMPLE_DEEP] = COLOR(QColor(Qt::red).darker(), BLACK1, PERSIANRED1); - profile_color[SAMPLE_SHALLOW] = COLOR(QColor(Qt::red).lighter(), BLACK1_LOW_TRANS, PERSIANRED1); - profile_color[SMOOTHED] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_LOW_TRANS, REDORANGE1_HIGH_TRANS); - profile_color[MINUTE] = COLOR(MEDIUMREDVIOLET1_HIGHER_TRANS, BLACK1_LOW_TRANS, MEDIUMREDVIOLET1_HIGHER_TRANS); - profile_color[TIME_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS); - profile_color[TIME_TEXT] = COLOR(FORESTGREEN1, BLACK1, FORESTGREEN1); - profile_color[DEPTH_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS); - profile_color[MEAN_DEPTH] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); - profile_color[HR_PLOT] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); - profile_color[HR_TEXT] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); - profile_color[HR_AXIS] = COLOR(MED_GRAY_HIGH_TRANS, MED_GRAY_HIGH_TRANS, MED_GRAY_HIGH_TRANS); - profile_color[DEPTH_BOTTOM] = COLOR(GOVERNORBAY1_MED_TRANS, BLACK1_HIGH_TRANS, GOVERNORBAY1_MED_TRANS); - profile_color[DEPTH_TOP] = COLOR(MERCURY1_MED_TRANS, WHITE1_MED_TRANS, MERCURY1_MED_TRANS); - profile_color[TEMP_TEXT] = COLOR(GOVERNORBAY2, BLACK1_LOW_TRANS, GOVERNORBAY2); - profile_color[TEMP_PLOT] = COLOR(ROYALBLUE2_LOW_TRANS, BLACK1_LOW_TRANS, ROYALBLUE2_LOW_TRANS); - profile_color[SAC_DEFAULT] = COLOR(WHITE1, BLACK1_LOW_TRANS, FORESTGREEN1); - profile_color[BOUNDING_BOX] = COLOR(WHITE1, BLACK1_LOW_TRANS, TUNDORA1_MED_TRANS); - profile_color[PRESSURE_TEXT] = COLOR(KILLARNEY1, BLACK1_LOW_TRANS, KILLARNEY1); - profile_color[BACKGROUND] = COLOR(SPRINGWOOD1, WHITE1, SPRINGWOOD1); - profile_color[BACKGROUND_TRANS] = COLOR(SPRINGWOOD1_MED_TRANS, WHITE1_MED_TRANS, SPRINGWOOD1_MED_TRANS); - profile_color[CEILING_SHALLOW] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_HIGH_TRANS, REDORANGE1_HIGH_TRANS); - profile_color[CEILING_DEEP] = COLOR(RED1_MED_TRANS, BLACK1_HIGH_TRANS, RED1_MED_TRANS); - profile_color[CALC_CEILING_SHALLOW] = COLOR(FUNGREEN1_HIGH_TRANS, BLACK1_HIGH_TRANS, FUNGREEN1_HIGH_TRANS); - profile_color[CALC_CEILING_DEEP] = COLOR(APPLE1_HIGH_TRANS, BLACK1_HIGH_TRANS, APPLE1_HIGH_TRANS); - profile_color[TISSUE_PERCENTAGE] = COLOR(GOVERNORBAY2, BLACK1_LOW_TRANS, GOVERNORBAY2); - profile_color[GF_LINE] = COLOR(BLACK1, BLACK1_LOW_TRANS, BLACK1); - profile_color[AMB_PRESSURE_LINE] = COLOR(TUNDORA1_MED_TRANS, BLACK1_LOW_TRANS, ATLANTIS1); -#undef COLOR -} - -QColor getColor(const color_indice_t i, bool isGrayscale) -{ - if (profile_color.count() > i && i >= 0) - return profile_color[i].at((isGrayscale) ? 1 : 0); - return QColor(Qt::black); -} - -QColor getSacColor(int sac, int avg_sac) -{ - int sac_index = 0; - int delta = sac - avg_sac + 7000; - - sac_index = delta / 2000; - if (sac_index < 0) - sac_index = 0; - if (sac_index > SAC_COLORS - 1) - sac_index = SAC_COLORS - 1; - return getColor((color_indice_t)(SAC_COLORS_START_IDX + sac_index), false); -} diff --git a/subsurface-core/color.h b/subsurface-core/color.h deleted file mode 100644 index 57ad77242..000000000 --- a/subsurface-core/color.h +++ /dev/null @@ -1,152 +0,0 @@ -#ifndef COLOR_H -#define COLOR_H - -/* The colors are named by picking the closest match - from http://chir.ag/projects/name-that-color */ - -#include <QColor> -#include <QMap> -#include <QVector> - -// Greens -#define CAMARONE1 QColor::fromRgbF(0.0, 0.4, 0.0, 1) -#define FUNGREEN1 QColor::fromRgbF(0.0, 0.4, 0.2, 1) -#define FUNGREEN1_HIGH_TRANS QColor::fromRgbF(0.0, 0.4, 0.2, 0.25) -#define KILLARNEY1 QColor::fromRgbF(0.2, 0.4, 0.2, 1) -#define APPLE1 QColor::fromRgbF(0.2, 0.6, 0.2, 1) -#define APPLE1_MED_TRANS QColor::fromRgbF(0.2, 0.6, 0.2, 0.5) -#define APPLE1_HIGH_TRANS QColor::fromRgbF(0.2, 0.6, 0.2, 0.25) -#define LIMENADE1 QColor::fromRgbF(0.4, 0.8, 0.0, 1) -#define ATLANTIS1 QColor::fromRgbF(0.4, 0.8, 0.2, 1) -#define ATLANTIS2 QColor::fromRgbF(0.6, 0.8, 0.2, 1) -#define RIOGRANDE1 QColor::fromRgbF(0.8, 0.8, 0.0, 1) -#define EARLSGREEN1 QColor::fromRgbF(0.8, 0.8, 0.2, 1) -#define FORESTGREEN1 QColor::fromRgbF(0.1, 0.5, 0.1, 1) -#define NITROX_GREEN QColor::fromRgbF(0, 0.54, 0.375, 1) - -// Reds -#define PERSIANRED1 QColor::fromRgbF(0.8, 0.2, 0.2, 1) -#define TUSCANY1 QColor::fromRgbF(0.8, 0.4, 0.2, 1) -#define PIRATEGOLD1 QColor::fromRgbF(0.8, 0.5, 0.0, 1) -#define HOKEYPOKEY1 QColor::fromRgbF(0.8, 0.6, 0.2, 1) -#define CINNABAR1 QColor::fromRgbF(0.9, 0.3, 0.2, 1) -#define REDORANGE1 QColor::fromRgbF(1.0, 0.2, 0.2, 1) -#define REDORANGE1_HIGH_TRANS QColor::fromRgbF(1.0, 0.2, 0.2, 0.25) -#define REDORANGE1_MED_TRANS QColor::fromRgbF(1.0, 0.2, 0.2, 0.5) -#define RED1_MED_TRANS QColor::fromRgbF(1.0, 0.0, 0.0, 0.5) -#define RED1 QColor::fromRgbF(1.0, 0.0, 0.0, 1) - -// Monochromes -#define BLACK1 QColor::fromRgbF(0.0, 0.0, 0.0, 1) -#define BLACK1_LOW_TRANS QColor::fromRgbF(0.0, 0.0, 0.0, 0.75) -#define BLACK1_HIGH_TRANS QColor::fromRgbF(0.0, 0.0, 0.0, 0.25) -#define TUNDORA1_MED_TRANS QColor::fromRgbF(0.3, 0.3, 0.3, 0.5) -#define MED_GRAY_HIGH_TRANS QColor::fromRgbF(0.5, 0.5, 0.5, 0.25) -#define MERCURY1_MED_TRANS QColor::fromRgbF(0.9, 0.9, 0.9, 0.5) -#define CONCRETE1_LOWER_TRANS QColor::fromRgbF(0.95, 0.95, 0.95, 0.9) -#define WHITE1_MED_TRANS QColor::fromRgbF(1.0, 1.0, 1.0, 0.5) -#define WHITE1 QColor::fromRgbF(1.0, 1.0, 1.0, 1) - -// Blues -#define GOVERNORBAY2 QColor::fromRgbF(0.2, 0.2, 0.7, 1) -#define GOVERNORBAY1_MED_TRANS QColor::fromRgbF(0.2, 0.2, 0.8, 0.5) -#define ROYALBLUE2 QColor::fromRgbF(0.2, 0.2, 0.9, 1) -#define ROYALBLUE2_LOW_TRANS QColor::fromRgbF(0.2, 0.2, 0.9, 0.75) -#define AIR_BLUE QColor::fromRgbF(0.25, 0.75, 1.0, 1) -#define AIR_BLUE_TRANS QColor::fromRgbF(0.25, 0.75, 1.0, 0.5) - -// Yellows / BROWNS -#define SPRINGWOOD1 QColor::fromRgbF(0.95, 0.95, 0.9, 1) -#define SPRINGWOOD1_MED_TRANS QColor::fromRgbF(0.95, 0.95, 0.9, 0.5) -#define BROOM1_LOWER_TRANS QColor::fromRgbF(1.0, 1.0, 0.1, 0.9) -#define PEANUT QColor::fromRgbF(0.5, 0.2, 0.1, 1.0) -#define PEANUT_MED_TRANS QColor::fromRgbF(0.5, 0.2, 0.1, 0.5) -#define NITROX_YELLOW QColor::fromRgbF(0.98, 0.89, 0.07, 1.0) - -// Magentas -#define MEDIUMREDVIOLET1_HIGHER_TRANS QColor::fromRgbF(0.7, 0.2, 0.7, 0.1) - -#define SAC_COLORS_START_IDX SAC_1 -#define SAC_COLORS 9 -#define VELOCITY_COLORS_START_IDX VELO_STABLE -#define VELOCITY_COLORS 5 - -typedef enum { - /* SAC colors. Order is important, the SAC_COLORS_START_IDX define above. */ - SAC_1, - SAC_2, - SAC_3, - SAC_4, - SAC_5, - SAC_6, - SAC_7, - SAC_8, - SAC_9, - - /* Velocity colors. Order is still important, ref VELOCITY_COLORS_START_IDX. */ - VELO_STABLE, - VELO_SLOW, - VELO_MODERATE, - VELO_FAST, - VELO_CRAZY, - - /* gas colors */ - PO2, - PO2_ALERT, - PN2, - PN2_ALERT, - PHE, - PHE_ALERT, - O2SETPOINT, - CCRSENSOR1, - CCRSENSOR2, - CCRSENSOR3, - PP_LINES, - - /* Other colors */ - TEXT_BACKGROUND, - ALERT_BG, - ALERT_FG, - EVENTS, - SAMPLE_DEEP, - SAMPLE_SHALLOW, - SMOOTHED, - MINUTE, - TIME_GRID, - TIME_TEXT, - DEPTH_GRID, - MEAN_DEPTH, - HR_TEXT, - HR_PLOT, - HR_AXIS, - DEPTH_TOP, - DEPTH_BOTTOM, - TEMP_TEXT, - TEMP_PLOT, - SAC_DEFAULT, - BOUNDING_BOX, - PRESSURE_TEXT, - BACKGROUND, - BACKGROUND_TRANS, - CEILING_SHALLOW, - CEILING_DEEP, - CALC_CEILING_SHALLOW, - CALC_CEILING_DEEP, - TISSUE_PERCENTAGE, - GF_LINE, - AMB_PRESSURE_LINE -} color_indice_t; - -extern QMap<color_indice_t, QVector<QColor> > profile_color; -void fill_profile_color(); -QColor getColor(const color_indice_t i, bool isGrayscale = false); -QColor getSacColor(int sac, int diveSac); -struct text_render_options { - double size; - color_indice_t color; - double hpos, vpos; -}; - -typedef text_render_options text_render_options_t; - -#endif // COLOR_H diff --git a/subsurface-core/compressibility.r b/subsurface-core/compressibility.r deleted file mode 100644 index 66310f3aa..000000000 --- a/subsurface-core/compressibility.r +++ /dev/null @@ -1,115 +0,0 @@ -# Compressibility data gathered by Lubomir I Ivanov: -# -# "Data obtained by finding two books online: -# -# [1] -# PERRY’S CHEMICAL ENGINEERS’ HANDBOOK SEVENTH EDITION -# pretty serious book, from which the wiki AIR values come from! -# -# http://www.unhas.ac.id/rhiza/arsip/kuliah/Sistem-dan-Tekn-Kendali-Proses/PDF_Collections/REFERENSI/Perrys_Chemical_Engineering_Handbook.pdf -# page 2-165 -# -# [*](Computed from pressure-volume-temperature tables in Vasserman monographs) -# ^ i have no idea idea what this means, but the values might not be exactly -# experimental?! -# -# the only thing this book is missing is helium, thus [2]! -# -# [2] -# VOLUMETRIC BEHAVIOR OF HELIUM-ARGON MIXTURES AT HIGH PRESSURE AND MODERATE TEMPERATURE. -# -# https://shareok.org/bitstream/handle/11244/2062/6614196.PDF?sequence=1 -# page 108 -# -# -# the book has some tables with pressure values in atmosphere units. i'm -# converting them bars. one of the relevant tables is for 323K and one for 273K -# (both almost equal distance from 300K). -# -# this again is a linear mix operation between isotherms, which is probably not -# the most accurate solution but it works. -# -# all data sets contain Z values at 300k, while the pressures are in bars in -# the 1 to 500 range -# -# - -x = c(1, 5, 10, 20, 40, 60, 80, 100, 200, 300, 400, 500) -o2 = c(0.9994, 0.9968, 0.9941, 0.9884, 0.9771, 0.9676, 0.9597, 0.9542, 0.9560, 0.9972, 1.0689, 1.1572) -n2 = c(0.9998, 0.9990, 0.9983, 0.9971, 0.9964, 0.9973, 1.0000, 1.0052, 1.0559, 1.1422, 1.2480, 1.3629) -he = c(1.0005, 1.0024, 1.0048, 1.0096, 1.0191, 1.0286, 1.0381, 1.0476, 1.0943, 1.1402, 1.1854, 1.2297) - -options(digits=15) - -# -# Get the O2 virial coefficients -# -plot(x,o2) -o2fit = nls(o2 ~ 1.0 + p1*x + p2 *x^2 + p3*x^3, start=list(p1=0,p2=0,p3=0)) -summary(o2fit) - -new = data.frame(x = seq(min(x),max(x),len=200)) -lines(new$x,predict(o2fit,newdata=new)) - -# -# Get the N2 virial coefficients -# -plot(x,n2) -n2fit = nls(n2 ~ 1.0 + p1*x + p2 *x^2 + p3*x^3, start=list(p1=0,p2=0,p3=0)) -summary(n2fit) - -new = data.frame(x = seq(min(x),max(x),len=200)) -lines(new$x,predict(n2fit,newdata=new)) - -# -# Get the He virial coefficients -# -# NOTE! This will not confirm convergence, thus the warnOnly. -# That may be a sign that the data is possibly artificial. -# -plot(x,he) -hefit = nls(he ~ 1.0 + p1*x + p2 *x^2 + p3*x^3, - start=list(p1=0,p2=0,p3=0), - control=nls.control(warnOnly=TRUE)) -summary(hefit) - -new = data.frame(x = seq(min(x),max(x),len=200)) -lines(new$x,predict(hefit,newdata=new)) - -# -# Raw data from VOLUMETRIC BEHAVIOR OF HELIUM-ARGON MIXTURES [..] -# T=323.15K (50 C) -p323atm = c(674.837, 393.223, 237.310, 146.294, 91.4027, 57.5799, 36.4620, 23.1654, 14.7478, 9.4017, 5.9987, 3.8300, - 540.204, 319.943, 195.008, 120.951, 75.8599, 47.9005, 30.3791, 19.3193, 12.3080, 7.8495, 5.0100, 3.1992) - -Hez323 = c(1.28067, 1.16782, 1.10289, 1.06407, 1.04028, 1.02548, 1.01617, 1.01029, 1.00656, 1.00418, 1.00267, 1.00171, - 1.22738, 1.13754, 1.08493, 1.05312, 1.03349, 1.02122, 1.01349, 1.00859, 1.00548, 1.00349, 1.00223, 1.00143) - - -# T=273.15 (0 C) -p273atm = c(683.599, 391.213, 233.607, 143.091, 89.0521, 55.9640, 35.3851, 22.4593, 14.2908, 9.1072, 5.8095, 3.7083, - 534.047, 312.144, 188.741, 116.508, 72.8529, 45.9194, 29.0883, 18.4851, 11.7702, 7.5040, 4.7881, 3.0570) - -Hez273 = c(1.33969, 1.19985, 1.12121, 1.07494, 1.04689, 1.02957, 1.01874, 1.01191, 1.00758, 1.00484, 1.00309, 1.00197, - 1.26914, 1.16070, 1.09837, 1.06118, 1.03843, 1.02429, 1.01541, 1.00980, 1.00625, 1.00398, 1.00254, 1.00162) - -p323 = p323atm * 1.01325 -p273 = p273atm * 1.01325 - -x2=append(p323,p273) -he2=append(Hez323,Hez273) - -plot(x2,he2) - -hefit2 = nls(he2 ~ 1.0 + p1*x2 + p2*x2^2 + p3*x2^3, - start=list(p1=0,p2=0,p3=0)) -summary(hefit2) - -he3 = function(bar) -{ - 1.0 +0.00047961098687979363 * bar -0.00000004077670019935 * bar^2 +0.00000000000077707035 * bar^3 -} - -new = data.frame(x2 = seq(min(x2),max(x2),len=200)) -lines(new$x2,predict(hefit2,newdata=new)) -curve(he3, min(x2),max(x2),add=TRUE) diff --git a/subsurface-core/configuredivecomputer.cpp b/subsurface-core/configuredivecomputer.cpp deleted file mode 100644 index 2457ffe82..000000000 --- a/subsurface-core/configuredivecomputer.cpp +++ /dev/null @@ -1,681 +0,0 @@ -#include "configuredivecomputer.h" -#include "libdivecomputer/hw.h" -#include <QTextStream> -#include <QFile> -#include <libxml/parser.h> -#include <libxml/parserInternals.h> -#include <libxml/tree.h> -#include <libxslt/transform.h> -#include <QStringList> -#include <QXmlStreamWriter> - -ConfigureDiveComputer::ConfigureDiveComputer() : readThread(0), - writeThread(0), - resetThread(0), - firmwareThread(0) -{ - setState(INITIAL); -} - -void ConfigureDiveComputer::readSettings(device_data_t *data) -{ - setState(READING); - - if (readThread) - readThread->deleteLater(); - - readThread = new ReadSettingsThread(this, data); - connect(readThread, SIGNAL(finished()), - this, SLOT(readThreadFinished()), Qt::QueuedConnection); - connect(readThread, SIGNAL(error(QString)), this, SLOT(setError(QString))); - connect(readThread, SIGNAL(devicedetails(DeviceDetails *)), this, - SIGNAL(deviceDetailsChanged(DeviceDetails *))); - connect(readThread, SIGNAL(progress(int)), this, SLOT(progressEvent(int)), Qt::QueuedConnection); - - readThread->start(); -} - -void ConfigureDiveComputer::saveDeviceDetails(DeviceDetails *details, device_data_t *data) -{ - setState(WRITING); - - if (writeThread) - writeThread->deleteLater(); - - writeThread = new WriteSettingsThread(this, data); - connect(writeThread, SIGNAL(finished()), - this, SLOT(writeThreadFinished()), Qt::QueuedConnection); - connect(writeThread, SIGNAL(error(QString)), this, SLOT(setError(QString))); - connect(writeThread, SIGNAL(progress(int)), this, SLOT(progressEvent(int)), Qt::QueuedConnection); - - writeThread->setDeviceDetails(details); - writeThread->start(); -} - -bool ConfigureDiveComputer::saveXMLBackup(QString fileName, DeviceDetails *details, device_data_t *data) -{ - QString xml = ""; - QString vendor = data->vendor; - QString product = data->product; - QXmlStreamWriter writer(&xml); - writer.setAutoFormatting(true); - - writer.writeStartDocument(); - writer.writeStartElement("DiveComputerSettingsBackup"); - writer.writeStartElement("DiveComputer"); - writer.writeTextElement("Vendor", vendor); - writer.writeTextElement("Product", product); - writer.writeEndElement(); - writer.writeStartElement("Settings"); - writer.writeTextElement("CustomText", details->customText); - //Add gasses - QString gas1 = QString("%1,%2,%3,%4") - .arg(QString::number(details->gas1.oxygen), - QString::number(details->gas1.helium), - QString::number(details->gas1.type), - QString::number(details->gas1.depth)); - QString gas2 = QString("%1,%2,%3,%4") - .arg(QString::number(details->gas2.oxygen), - QString::number(details->gas2.helium), - QString::number(details->gas2.type), - QString::number(details->gas2.depth)); - QString gas3 = QString("%1,%2,%3,%4") - .arg(QString::number(details->gas3.oxygen), - QString::number(details->gas3.helium), - QString::number(details->gas3.type), - QString::number(details->gas3.depth)); - QString gas4 = QString("%1,%2,%3,%4") - .arg(QString::number(details->gas4.oxygen), - QString::number(details->gas4.helium), - QString::number(details->gas4.type), - QString::number(details->gas4.depth)); - QString gas5 = QString("%1,%2,%3,%4") - .arg(QString::number(details->gas5.oxygen), - QString::number(details->gas5.helium), - QString::number(details->gas5.type), - QString::number(details->gas5.depth)); - writer.writeTextElement("Gas1", gas1); - writer.writeTextElement("Gas2", gas2); - writer.writeTextElement("Gas3", gas3); - writer.writeTextElement("Gas4", gas4); - writer.writeTextElement("Gas5", gas5); - // - //Add dil values - QString dil1 = QString("%1,%2,%3,%4") - .arg(QString::number(details->dil1.oxygen), - QString::number(details->dil1.helium), - QString::number(details->dil1.type), - QString::number(details->dil1.depth)); - QString dil2 = QString("%1,%2,%3,%4") - .arg(QString::number(details->dil2.oxygen), - QString::number(details->dil2.helium), - QString::number(details->dil2.type), - QString::number(details->dil2.depth)); - QString dil3 = QString("%1,%2,%3,%4") - .arg(QString::number(details->dil3.oxygen), - QString::number(details->dil3.helium), - QString::number(details->dil3.type), - QString::number(details->dil3.depth)); - QString dil4 = QString("%1,%2,%3,%4") - .arg(QString::number(details->dil4.oxygen), - QString::number(details->dil4.helium), - QString::number(details->dil4.type), - QString::number(details->dil4.depth)); - QString dil5 = QString("%1,%2,%3,%4") - .arg(QString::number(details->dil5.oxygen), - QString::number(details->dil5.helium), - QString::number(details->dil5.type), - QString::number(details->dil5.depth)); - writer.writeTextElement("Dil1", dil1); - writer.writeTextElement("Dil2", dil2); - writer.writeTextElement("Dil3", dil3); - writer.writeTextElement("Dil4", dil4); - writer.writeTextElement("Dil5", dil5); - // - //Add set point values - QString sp1 = QString("%1,%2") - .arg(QString::number(details->sp1.sp), - QString::number(details->sp1.depth)); - QString sp2 = QString("%1,%2") - .arg(QString::number(details->sp2.sp), - QString::number(details->sp2.depth)); - QString sp3 = QString("%1,%2") - .arg(QString::number(details->sp3.sp), - QString::number(details->sp3.depth)); - QString sp4 = QString("%1,%2") - .arg(QString::number(details->sp4.sp), - QString::number(details->sp4.depth)); - QString sp5 = QString("%1,%2") - .arg(QString::number(details->sp5.sp), - QString::number(details->sp5.depth)); - writer.writeTextElement("SetPoint1", sp1); - writer.writeTextElement("SetPoint2", sp2); - writer.writeTextElement("SetPoint3", sp3); - writer.writeTextElement("SetPoint4", sp4); - writer.writeTextElement("SetPoint5", sp5); - - //Other Settings - writer.writeTextElement("DiveMode", QString::number(details->diveMode)); - writer.writeTextElement("Saturation", QString::number(details->saturation)); - writer.writeTextElement("Desaturation", QString::number(details->desaturation)); - writer.writeTextElement("LastDeco", QString::number(details->lastDeco)); - writer.writeTextElement("Brightness", QString::number(details->brightness)); - writer.writeTextElement("Units", QString::number(details->units)); - writer.writeTextElement("SamplingRate", QString::number(details->samplingRate)); - writer.writeTextElement("Salinity", QString::number(details->salinity)); - writer.writeTextElement("DiveModeColor", QString::number(details->diveModeColor)); - writer.writeTextElement("Language", QString::number(details->language)); - writer.writeTextElement("DateFormat", QString::number(details->dateFormat)); - writer.writeTextElement("CompassGain", QString::number(details->compassGain)); - writer.writeTextElement("SafetyStop", QString::number(details->safetyStop)); - writer.writeTextElement("GfHigh", QString::number(details->gfHigh)); - writer.writeTextElement("GfLow", QString::number(details->gfLow)); - writer.writeTextElement("PressureSensorOffset", QString::number(details->pressureSensorOffset)); - writer.writeTextElement("PpO2Min", QString::number(details->ppO2Min)); - writer.writeTextElement("PpO2Max", QString::number(details->ppO2Max)); - writer.writeTextElement("FutureTTS", QString::number(details->futureTTS)); - writer.writeTextElement("CcrMode", QString::number(details->ccrMode)); - writer.writeTextElement("DecoType", QString::number(details->decoType)); - writer.writeTextElement("AGFSelectable", QString::number(details->aGFSelectable)); - writer.writeTextElement("AGFHigh", QString::number(details->aGFHigh)); - writer.writeTextElement("AGFLow", QString::number(details->aGFLow)); - writer.writeTextElement("CalibrationGas", QString::number(details->calibrationGas)); - writer.writeTextElement("FlipScreen", QString::number(details->flipScreen)); - writer.writeTextElement("SetPointFallback", QString::number(details->setPointFallback)); - writer.writeTextElement("LeftButtonSensitivity", QString::number(details->leftButtonSensitivity)); - writer.writeTextElement("RightButtonSensitivity", QString::number(details->rightButtonSensitivity)); - writer.writeTextElement("BottomGasConsumption", QString::number(details->bottomGasConsumption)); - writer.writeTextElement("DecoGasConsumption", QString::number(details->decoGasConsumption)); - writer.writeTextElement("ModWarning", QString::number(details->modWarning)); - writer.writeTextElement("DynamicAscendRate", QString::number(details->dynamicAscendRate)); - writer.writeTextElement("GraphicalSpeedIndicator", QString::number(details->graphicalSpeedIndicator)); - writer.writeTextElement("AlwaysShowppO2", QString::number(details->alwaysShowppO2)); - - // Suunto vyper settings. - writer.writeTextElement("Altitude", QString::number(details->altitude)); - writer.writeTextElement("PersonalSafety", QString::number(details->personalSafety)); - writer.writeTextElement("TimeFormat", QString::number(details->timeFormat)); - - writer.writeStartElement("Light"); - writer.writeAttribute("enabled", QString::number(details->lightEnabled)); - writer.writeCharacters(QString::number(details->light)); - writer.writeEndElement(); - - writer.writeStartElement("AlarmTime"); - writer.writeAttribute("enabled", QString::number(details->alarmTimeEnabled)); - writer.writeCharacters(QString::number(details->alarmTime)); - writer.writeEndElement(); - - writer.writeStartElement("AlarmDepth"); - writer.writeAttribute("enabled", QString::number(details->alarmDepthEnabled)); - writer.writeCharacters(QString::number(details->alarmDepth)); - writer.writeEndElement(); - - writer.writeEndElement(); - writer.writeEndElement(); - - writer.writeEndDocument(); - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - lastError = tr("Could not save the backup file %1. Error Message: %2") - .arg(fileName, file.errorString()); - return false; - } - //file open successful. write data and save. - QTextStream out(&file); - out << xml; - - file.close(); - return true; -} - -bool ConfigureDiveComputer::restoreXMLBackup(QString fileName, DeviceDetails *details) -{ - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - lastError = tr("Could not open backup file: %1").arg(file.errorString()); - return false; - } - - QString xml = file.readAll(); - - QXmlStreamReader reader(xml); - while (!reader.atEnd()) { - if (reader.isStartElement()) { - QString settingName = reader.name().toString(); - QXmlStreamAttributes attributes = reader.attributes(); - reader.readNext(); - QString keyString = reader.text().toString(); - - if (settingName == "CustomText") - details->customText = keyString; - - if (settingName == "Gas1") { - QStringList gasData = keyString.split(","); - gas gas1; - gas1.oxygen = gasData.at(0).toInt(); - gas1.helium = gasData.at(1).toInt(); - gas1.type = gasData.at(2).toInt(); - gas1.depth = gasData.at(3).toInt(); - details->gas1 = gas1; - } - - if (settingName == "Gas2") { - QStringList gasData = keyString.split(","); - gas gas2; - gas2.oxygen = gasData.at(0).toInt(); - gas2.helium = gasData.at(1).toInt(); - gas2.type = gasData.at(2).toInt(); - gas2.depth = gasData.at(3).toInt(); - details->gas2 = gas2; - } - - if (settingName == "Gas3") { - QStringList gasData = keyString.split(","); - gas gas3; - gas3.oxygen = gasData.at(0).toInt(); - gas3.helium = gasData.at(1).toInt(); - gas3.type = gasData.at(2).toInt(); - gas3.depth = gasData.at(3).toInt(); - details->gas3 = gas3; - } - - if (settingName == "Gas4") { - QStringList gasData = keyString.split(","); - gas gas4; - gas4.oxygen = gasData.at(0).toInt(); - gas4.helium = gasData.at(1).toInt(); - gas4.type = gasData.at(2).toInt(); - gas4.depth = gasData.at(3).toInt(); - details->gas4 = gas4; - } - - if (settingName == "Gas5") { - QStringList gasData = keyString.split(","); - gas gas5; - gas5.oxygen = gasData.at(0).toInt(); - gas5.helium = gasData.at(1).toInt(); - gas5.type = gasData.at(2).toInt(); - gas5.depth = gasData.at(3).toInt(); - details->gas5 = gas5; - } - - if (settingName == "Dil1") { - QStringList dilData = keyString.split(","); - gas dil1; - dil1.oxygen = dilData.at(0).toInt(); - dil1.helium = dilData.at(1).toInt(); - dil1.type = dilData.at(2).toInt(); - dil1.depth = dilData.at(3).toInt(); - details->dil1 = dil1; - } - - if (settingName == "Dil2") { - QStringList dilData = keyString.split(","); - gas dil2; - dil2.oxygen = dilData.at(0).toInt(); - dil2.helium = dilData.at(1).toInt(); - dil2.type = dilData.at(2).toInt(); - dil2.depth = dilData.at(3).toInt(); - details->dil1 = dil2; - } - - if (settingName == "Dil3") { - QStringList dilData = keyString.split(","); - gas dil3; - dil3.oxygen = dilData.at(0).toInt(); - dil3.helium = dilData.at(1).toInt(); - dil3.type = dilData.at(2).toInt(); - dil3.depth = dilData.at(3).toInt(); - details->dil3 = dil3; - } - - if (settingName == "Dil4") { - QStringList dilData = keyString.split(","); - gas dil4; - dil4.oxygen = dilData.at(0).toInt(); - dil4.helium = dilData.at(1).toInt(); - dil4.type = dilData.at(2).toInt(); - dil4.depth = dilData.at(3).toInt(); - details->dil4 = dil4; - } - - if (settingName == "Dil5") { - QStringList dilData = keyString.split(","); - gas dil5; - dil5.oxygen = dilData.at(0).toInt(); - dil5.helium = dilData.at(1).toInt(); - dil5.type = dilData.at(2).toInt(); - dil5.depth = dilData.at(3).toInt(); - details->dil5 = dil5; - } - - if (settingName == "SetPoint1") { - QStringList spData = keyString.split(","); - setpoint sp1; - sp1.sp = spData.at(0).toInt(); - sp1.depth = spData.at(1).toInt(); - details->sp1 = sp1; - } - - if (settingName == "SetPoint2") { - QStringList spData = keyString.split(","); - setpoint sp2; - sp2.sp = spData.at(0).toInt(); - sp2.depth = spData.at(1).toInt(); - details->sp2 = sp2; - } - - if (settingName == "SetPoint3") { - QStringList spData = keyString.split(","); - setpoint sp3; - sp3.sp = spData.at(0).toInt(); - sp3.depth = spData.at(1).toInt(); - details->sp3 = sp3; - } - - if (settingName == "SetPoint4") { - QStringList spData = keyString.split(","); - setpoint sp4; - sp4.sp = spData.at(0).toInt(); - sp4.depth = spData.at(1).toInt(); - details->sp4 = sp4; - } - - if (settingName == "SetPoint5") { - QStringList spData = keyString.split(","); - setpoint sp5; - sp5.sp = spData.at(0).toInt(); - sp5.depth = spData.at(1).toInt(); - details->sp5 = sp5; - } - - if (settingName == "Saturation") - details->saturation = keyString.toInt(); - - if (settingName == "Desaturation") - details->desaturation = keyString.toInt(); - - if (settingName == "DiveMode") - details->diveMode = keyString.toInt(); - - if (settingName == "LastDeco") - details->lastDeco = keyString.toInt(); - - if (settingName == "Brightness") - details->brightness = keyString.toInt(); - - if (settingName == "Units") - details->units = keyString.toInt(); - - if (settingName == "SamplingRate") - details->samplingRate = keyString.toInt(); - - if (settingName == "Salinity") - details->salinity = keyString.toInt(); - - if (settingName == "DiveModeColour") - details->diveModeColor = keyString.toInt(); - - if (settingName == "Language") - details->language = keyString.toInt(); - - if (settingName == "DateFormat") - details->dateFormat = keyString.toInt(); - - if (settingName == "CompassGain") - details->compassGain = keyString.toInt(); - - if (settingName == "SafetyStop") - details->safetyStop = keyString.toInt(); - - if (settingName == "GfHigh") - details->gfHigh = keyString.toInt(); - - if (settingName == "GfLow") - details->gfLow = keyString.toInt(); - - if (settingName == "PressureSensorOffset") - details->pressureSensorOffset = keyString.toInt(); - - if (settingName == "PpO2Min") - details->ppO2Min = keyString.toInt(); - - if (settingName == "PpO2Max") - details->ppO2Max = keyString.toInt(); - - if (settingName == "FutureTTS") - details->futureTTS = keyString.toInt(); - - if (settingName == "CcrMode") - details->ccrMode = keyString.toInt(); - - if (settingName == "DecoType") - details->decoType = keyString.toInt(); - - if (settingName == "AGFSelectable") - details->aGFSelectable = keyString.toInt(); - - if (settingName == "AGFHigh") - details->aGFHigh = keyString.toInt(); - - if (settingName == "AGFLow") - details->aGFLow = keyString.toInt(); - - if (settingName == "CalibrationGas") - details->calibrationGas = keyString.toInt(); - - if (settingName == "FlipScreen") - details->flipScreen = keyString.toInt(); - - if (settingName == "SetPointFallback") - details->setPointFallback = keyString.toInt(); - - if (settingName == "LeftButtonSensitivity") - details->leftButtonSensitivity = keyString.toInt(); - - if (settingName == "RightButtonSensitivity") - details->rightButtonSensitivity = keyString.toInt(); - - if (settingName == "BottomGasConsumption") - details->bottomGasConsumption = keyString.toInt(); - - if (settingName == "DecoGasConsumption") - details->decoGasConsumption = keyString.toInt(); - - if (settingName == "ModWarning") - details->modWarning = keyString.toInt(); - - if (settingName == "DynamicAscendRate") - details->dynamicAscendRate = keyString.toInt(); - - if (settingName == "GraphicalSpeedIndicator") - details->graphicalSpeedIndicator = keyString.toInt(); - - if (settingName == "AlwaysShowppO2") - details->alwaysShowppO2 = keyString.toInt(); - - if (settingName == "Altitude") - details->altitude = keyString.toInt(); - - if (settingName == "PersonalSafety") - details->personalSafety = keyString.toInt(); - - if (settingName == "TimeFormat") - details->timeFormat = keyString.toInt(); - - if (settingName == "Light") { - if (attributes.hasAttribute("enabled")) - details->lightEnabled = attributes.value("enabled").toString().toInt(); - details->light = keyString.toInt(); - } - - if (settingName == "AlarmDepth") { - if (attributes.hasAttribute("enabled")) - details->alarmDepthEnabled = attributes.value("enabled").toString().toInt(); - details->alarmDepth = keyString.toInt(); - } - - if (settingName == "AlarmTime") { - if (attributes.hasAttribute("enabled")) - details->alarmTimeEnabled = attributes.value("enabled").toString().toInt(); - details->alarmTime = keyString.toInt(); - } - } - reader.readNext(); - } - - return true; -} - -void ConfigureDiveComputer::startFirmwareUpdate(QString fileName, device_data_t *data) -{ - setState(FWUPDATE); - if (firmwareThread) - firmwareThread->deleteLater(); - - firmwareThread = new FirmwareUpdateThread(this, data, fileName); - connect(firmwareThread, SIGNAL(finished()), - this, SLOT(firmwareThreadFinished()), Qt::QueuedConnection); - connect(firmwareThread, SIGNAL(error(QString)), this, SLOT(setError(QString))); - connect(firmwareThread, SIGNAL(progress(int)), this, SLOT(progressEvent(int)), Qt::QueuedConnection); - - firmwareThread->start(); -} - -void ConfigureDiveComputer::resetSettings(device_data_t *data) -{ - setState(RESETTING); - - if (resetThread) - resetThread->deleteLater(); - - resetThread = new ResetSettingsThread(this, data); - connect(resetThread, SIGNAL(finished()), - this, SLOT(resetThreadFinished()), Qt::QueuedConnection); - connect(resetThread, SIGNAL(error(QString)), this, SLOT(setError(QString))); - connect(resetThread, SIGNAL(progress(int)), this, SLOT(progressEvent(int)), Qt::QueuedConnection); - - resetThread->start(); -} - -void ConfigureDiveComputer::progressEvent(int percent) -{ - emit progress(percent); -} - -void ConfigureDiveComputer::setState(ConfigureDiveComputer::states newState) -{ - currentState = newState; - emit stateChanged(currentState); -} - -void ConfigureDiveComputer::setError(QString err) -{ - lastError = err; - emit error(err); -} - -void ConfigureDiveComputer::readThreadFinished() -{ - setState(DONE); - if (lastError.isEmpty()) { - //No error - emit message(tr("Dive computer details read successfully")); - } -} - -void ConfigureDiveComputer::writeThreadFinished() -{ - setState(DONE); - if (lastError.isEmpty()) { - //No error - emit message(tr("Setting successfully written to device")); - } -} - -void ConfigureDiveComputer::firmwareThreadFinished() -{ - setState(DONE); - if (lastError.isEmpty()) { - //No error - emit message(tr("Device firmware successfully updated")); - } -} - -void ConfigureDiveComputer::resetThreadFinished() -{ - setState(DONE); - if (lastError.isEmpty()) { - //No error - emit message(tr("Device settings successfully reset")); - } -} - -QString ConfigureDiveComputer::dc_open(device_data_t *data) -{ - FILE *fp = NULL; - dc_status_t rc; - - if (data->libdc_log) - fp = subsurface_fopen(logfile_name, "w"); - - data->libdc_logfile = fp; - - rc = dc_context_new(&data->context); - if (rc != DC_STATUS_SUCCESS) { - return tr("Unable to create libdivecomputer context"); - } - - if (fp) { - dc_context_set_loglevel(data->context, DC_LOGLEVEL_ALL); - dc_context_set_logfunc(data->context, logfunc, fp); - } - -#if defined(SSRF_CUSTOM_SERIAL) - dc_serial_t *serial_device = NULL; - - if (data->bluetooth_mode) { -#ifdef BT_SUPPORT - rc = dc_serial_qt_open(&serial_device, data->context, data->devname); -#endif -#ifdef SERIAL_FTDI - } else if (!strcmp(data->devname, "ftdi")) { - rc = dc_serial_ftdi_open(&serial_device, data->context); -#endif - } - - if (rc != DC_STATUS_SUCCESS) { - return errmsg(rc); - } else if (serial_device) { - rc = dc_device_custom_open(&data->device, data->context, data->descriptor, serial_device); - } else { -#else - { -#endif - rc = dc_device_open(&data->device, data->context, data->descriptor, data->devname); - } - - if (rc != DC_STATUS_SUCCESS) { - return tr("Could not a establish connection to the dive computer."); - } - - setState(OPEN); - - return NULL; -} - -void ConfigureDiveComputer::dc_close(device_data_t *data) -{ - if (data->device) - dc_device_close(data->device); - data->device = NULL; - if (data->context) - dc_context_free(data->context); - data->context = NULL; - - if (data->libdc_logfile) - fclose(data->libdc_logfile); - - setState(INITIAL); -} diff --git a/subsurface-core/configuredivecomputer.h b/subsurface-core/configuredivecomputer.h deleted file mode 100644 index f14eeeca3..000000000 --- a/subsurface-core/configuredivecomputer.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef CONFIGUREDIVECOMPUTER_H -#define CONFIGUREDIVECOMPUTER_H - -#include <QObject> -#include <QThread> -#include <QVariant> -#include "libdivecomputer.h" -#include "configuredivecomputerthreads.h" -#include <QDateTime> - -#include "libxml/xmlreader.h" - -class ConfigureDiveComputer : public QObject { - Q_OBJECT -public: - explicit ConfigureDiveComputer(); - void readSettings(device_data_t *data); - - enum states { - OPEN, - INITIAL, - READING, - WRITING, - RESETTING, - FWUPDATE, - CANCELLING, - CANCELLED, - ERROR, - DONE, - }; - - QString lastError; - states currentState; - void saveDeviceDetails(DeviceDetails *details, device_data_t *data); - void fetchDeviceDetails(); - bool saveXMLBackup(QString fileName, DeviceDetails *details, device_data_t *data); - bool restoreXMLBackup(QString fileName, DeviceDetails *details); - void startFirmwareUpdate(QString fileName, device_data_t *data); - void resetSettings(device_data_t *data); - - QString dc_open(device_data_t *data); -public -slots: - void dc_close(device_data_t *data); -signals: - void progress(int percent); - void message(QString msg); - void error(QString err); - void stateChanged(states newState); - void deviceDetailsChanged(DeviceDetails *newDetails); - -private: - ReadSettingsThread *readThread; - WriteSettingsThread *writeThread; - ResetSettingsThread *resetThread; - FirmwareUpdateThread *firmwareThread; - void setState(states newState); -private -slots: - void progressEvent(int percent); - void readThreadFinished(); - void writeThreadFinished(); - void resetThreadFinished(); - void firmwareThreadFinished(); - void setError(QString err); -}; - -#endif // CONFIGUREDIVECOMPUTER_H diff --git a/subsurface-core/configuredivecomputerthreads.cpp b/subsurface-core/configuredivecomputerthreads.cpp deleted file mode 100644 index b229fc808..000000000 --- a/subsurface-core/configuredivecomputerthreads.cpp +++ /dev/null @@ -1,1778 +0,0 @@ -#include "configuredivecomputerthreads.h" -#include "libdivecomputer/hw.h" -#include "libdivecomputer.h" -#include <libdivecomputer/version.h> - -#define OSTC3_GAS1 0x10 -#define OSTC3_GAS2 0x11 -#define OSTC3_GAS3 0x12 -#define OSTC3_GAS4 0x13 -#define OSTC3_GAS5 0x14 -#define OSTC3_DIL1 0x15 -#define OSTC3_DIL2 0x16 -#define OSTC3_DIL3 0x17 -#define OSTC3_DIL4 0x18 -#define OSTC3_DIL5 0x19 -#define OSTC3_SP1 0x1A -#define OSTC3_SP2 0x1B -#define OSTC3_SP3 0x1C -#define OSTC3_SP4 0x1D -#define OSTC3_SP5 0x1E -#define OSTC3_CCR_MODE 0x1F -#define OSTC3_DIVE_MODE 0x20 -#define OSTC3_DECO_TYPE 0x21 -#define OSTC3_PPO2_MAX 0x22 -#define OSTC3_PPO2_MIN 0x23 -#define OSTC3_FUTURE_TTS 0x24 -#define OSTC3_GF_LOW 0x25 -#define OSTC3_GF_HIGH 0x26 -#define OSTC3_AGF_LOW 0x27 -#define OSTC3_AGF_HIGH 0x28 -#define OSTC3_AGF_SELECTABLE 0x29 -#define OSTC3_SATURATION 0x2A -#define OSTC3_DESATURATION 0x2B -#define OSTC3_LAST_DECO 0x2C -#define OSTC3_BRIGHTNESS 0x2D -#define OSTC3_UNITS 0x2E -#define OSTC3_SAMPLING_RATE 0x2F -#define OSTC3_SALINITY 0x30 -#define OSTC3_DIVEMODE_COLOR 0x31 -#define OSTC3_LANGUAGE 0x32 -#define OSTC3_DATE_FORMAT 0x33 -#define OSTC3_COMPASS_GAIN 0x34 -#define OSTC3_PRESSURE_SENSOR_OFFSET 0x35 -#define OSTC3_SAFETY_STOP 0x36 -#define OSTC3_CALIBRATION_GAS_O2 0x37 -#define OSTC3_SETPOINT_FALLBACK 0x38 -#define OSTC3_FLIP_SCREEN 0x39 -#define OSTC3_LEFT_BUTTON_SENSIVITY 0x3A -#define OSTC3_RIGHT_BUTTON_SENSIVITY 0x3A -#define OSTC3_BOTTOM_GAS_CONSUMPTION 0x3C -#define OSTC3_DECO_GAS_CONSUMPTION 0x3D -#define OSTC3_MOD_WARNING 0x3E -#define OSTC3_DYNAMIC_ASCEND_RATE 0x3F -#define OSTC3_GRAPHICAL_SPEED_INDICATOR 0x40 -#define OSTC3_ALWAYS_SHOW_PPO2 0x41 -#define OSTC3_TEMP_SENSOR_OFFSET 0x42 -#define OSTC3_SAFETY_STOP_LENGTH 0x43 -#define OSTC3_SAFETY_STOP_START_DEPTH 0x44 -#define OSTC3_SAFETY_STOP_END_DEPTH 0x45 -#define OSTC3_SAFETY_STOP_RESET_DEPTH 0x46 - -#define OSTC3_HW_OSTC_3 0x0A -#define OSTC3_HW_OSTC_3P 0x1A -#define OSTC3_HW_OSTC_CR 0x05 -#define OSTC3_HW_OSTC_SPORT 0x12 -#define OSTC3_HW_OSTC_2 0x11 - - -#define SUUNTO_VYPER_MAXDEPTH 0x1e -#define SUUNTO_VYPER_TOTAL_TIME 0x20 -#define SUUNTO_VYPER_NUMBEROFDIVES 0x22 -#define SUUNTO_VYPER_COMPUTER_TYPE 0x24 -#define SUUNTO_VYPER_FIRMWARE 0x25 -#define SUUNTO_VYPER_SERIALNUMBER 0x26 -#define SUUNTO_VYPER_CUSTOM_TEXT 0x2c -#define SUUNTO_VYPER_SAMPLING_RATE 0x53 -#define SUUNTO_VYPER_ALTITUDE_SAFETY 0x54 -#define SUUNTO_VYPER_TIMEFORMAT 0x60 -#define SUUNTO_VYPER_UNITS 0x62 -#define SUUNTO_VYPER_MODEL 0x63 -#define SUUNTO_VYPER_LIGHT 0x64 -#define SUUNTO_VYPER_ALARM_DEPTH_TIME 0x65 -#define SUUNTO_VYPER_ALARM_TIME 0x66 -#define SUUNTO_VYPER_ALARM_DEPTH 0x68 -#define SUUNTO_VYPER_CUSTOM_TEXT_LENGHT 30 - -#ifdef DEBUG_OSTC -// Fake io to ostc memory banks -#define hw_ostc_device_eeprom_read local_hw_ostc_device_eeprom_read -#define hw_ostc_device_eeprom_write local_hw_ostc_device_eeprom_write -#define hw_ostc_device_clock local_hw_ostc_device_clock -#define OSTC_FILE "../OSTC-data-dump.bin" - -// Fake the open function. -static dc_status_t local_dc_device_open(dc_device_t **out, dc_context_t *context, dc_descriptor_t *descriptor, const char *name) -{ - if (strcmp(dc_descriptor_get_vendor(descriptor), "Heinrichs Weikamp") == 0 &&strcmp(dc_descriptor_get_product(descriptor), "OSTC 2N") == 0) - return DC_STATUS_SUCCESS; - else - return dc_device_open(out, context, descriptor, name); -} -#define dc_device_open local_dc_device_open - -// Fake the custom open function -static dc_status_t local_dc_device_custom_open(dc_device_t **out, dc_context_t *context, dc_descriptor_t *descriptor, dc_serial_t *serial) -{ - if (strcmp(dc_descriptor_get_vendor(descriptor), "Heinrichs Weikamp") == 0 &&strcmp(dc_descriptor_get_product(descriptor), "OSTC 2N") == 0) - return DC_STATUS_SUCCESS; - else - return dc_device_custom_open(out, context, descriptor, serial); -} -#define dc_device_custom_open local_dc_device_custom_open - -static dc_status_t local_hw_ostc_device_eeprom_read(void *ignored, unsigned char bank, unsigned char data[], unsigned int data_size) -{ - FILE *f; - if ((f = fopen(OSTC_FILE, "r")) == NULL) - return DC_STATUS_NODEVICE; - fseek(f, bank * 256, SEEK_SET); - if (fread(data, sizeof(unsigned char), data_size, f) != data_size) { - fclose(f); - return DC_STATUS_IO; - } - fclose(f); - - return DC_STATUS_SUCCESS; -} - -static dc_status_t local_hw_ostc_device_eeprom_write(void *ignored, unsigned char bank, unsigned char data[], unsigned int data_size) -{ - FILE *f; - if ((f = fopen(OSTC_FILE, "r+")) == NULL) - f = fopen(OSTC_FILE, "w"); - fseek(f, bank * 256, SEEK_SET); - fwrite(data, sizeof(unsigned char), data_size, f); - fclose(f); - - return DC_STATUS_SUCCESS; -} - -static dc_status_t local_hw_ostc_device_clock(void *ignored, dc_datetime_t *time) -{ - return DC_STATUS_SUCCESS; -} -#endif - -static int read_ostc_cf(unsigned char data[], unsigned char cf) -{ - return data[128 + (cf % 32) * 4 + 3] << 8 ^ data[128 + (cf % 32) * 4 + 2]; -} - -static void write_ostc_cf(unsigned char data[], unsigned char cf, unsigned char max_CF, unsigned int value) -{ - // Only write settings supported by this firmware. - if (cf > max_CF) - return; - - data[128 + (cf % 32) * 4 + 3] = (value & 0xff00) >> 8; - data[128 + (cf % 32) * 4 + 2] = (value & 0x00ff); -} - -#define EMIT_PROGRESS() do { \ - progress.current++; \ - progress_cb(device, DC_EVENT_PROGRESS, &progress, userdata); \ - } while (0) - -static dc_status_t read_suunto_vyper_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - unsigned char data[SUUNTO_VYPER_CUSTOM_TEXT_LENGHT + 1]; - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 16; - - rc = dc_device_read(device, SUUNTO_VYPER_COMPUTER_TYPE, data, 1); - if (rc == DC_STATUS_SUCCESS) { - const char *model; - // FIXME: grab this info from libdivecomputer descriptor - // instead of hard coded here - switch (data[0]) { - case 0x03: - model = "Stinger"; - break; - case 0x04: - model = "Mosquito"; - break; - case 0x05: - model = "D3"; - break; - case 0x0A: - model = "Vyper"; - break; - case 0x0B: - model = "Vytec"; - break; - case 0x0C: - model = "Cobra"; - break; - case 0x0D: - model = "Gekko"; - break; - case 0x16: - model = "Zoop"; - break; - case 20: - case 30: - case 60: - // Suunto Spyder have there sample interval at this position - // Fallthrough - default: - return DC_STATUS_UNSUPPORTED; - } - // We found a supported device - // we can safely proceed with reading/writing to this device. - m_deviceDetails->model = model; - } - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_MAXDEPTH, data, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - // in ft * 128.0 - int depth = feet_to_mm(data[0] << 8 ^ data[1]) / 128; - m_deviceDetails->maxDepth = depth; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_TOTAL_TIME, data, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - int total_time = data[0] << 8 ^ data[1]; - m_deviceDetails->totalTime = total_time; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_NUMBEROFDIVES, data, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - int number_of_dives = data[0] << 8 ^ data[1]; - m_deviceDetails->numberOfDives = number_of_dives; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_FIRMWARE, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->firmwareVersion = QString::number(data[0]) + ".0.0"; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_SERIALNUMBER, data, 4); - if (rc != DC_STATUS_SUCCESS) - return rc; - int serial_number = data[0] * 1000000 + data[1] * 10000 + data[2] * 100 + data[3]; - m_deviceDetails->serialNo = QString::number(serial_number); - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_CUSTOM_TEXT, data, SUUNTO_VYPER_CUSTOM_TEXT_LENGHT); - if (rc != DC_STATUS_SUCCESS) - return rc; - data[SUUNTO_VYPER_CUSTOM_TEXT_LENGHT] = 0; - m_deviceDetails->customText = (const char *)data; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_SAMPLING_RATE, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->samplingRate = (int)data[0]; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_ALTITUDE_SAFETY, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->altitude = data[0] & 0x03; - m_deviceDetails->personalSafety = data[0] >> 2 & 0x03; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_TIMEFORMAT, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->timeFormat = data[0] & 0x01; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_UNITS, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->units = data[0] & 0x01; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_MODEL, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->diveMode = data[0] & 0x03; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_LIGHT, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->lightEnabled = data[0] >> 7; - m_deviceDetails->light = data[0] & 0x7F; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_ALARM_DEPTH_TIME, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->alarmTimeEnabled = data[0] & 0x01; - m_deviceDetails->alarmDepthEnabled = data[0] >> 1 & 0x01; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_ALARM_TIME, data, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - int time = data[0] << 8 ^ data[1]; - // The stinger stores alarm time in seconds instead of minutes. - if (m_deviceDetails->model == "Stinger") - time /= 60; - m_deviceDetails->alarmTime = time; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_ALARM_DEPTH, data, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - depth = feet_to_mm(data[0] << 8 ^ data[1]) / 128; - m_deviceDetails->alarmDepth = depth; - EMIT_PROGRESS(); - - return DC_STATUS_SUCCESS; -} - -static dc_status_t write_suunto_vyper_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 10; - unsigned char data; - unsigned char data2[2]; - int time; - - // Maybee we should read the model from the device to sanity check it here too.. - // For now we just check that we actually read a device before writing to one. - if (m_deviceDetails->model == "") - return DC_STATUS_UNSUPPORTED; - - rc = dc_device_write(device, SUUNTO_VYPER_CUSTOM_TEXT, - // Convert the customText to a 30 char wide padded with " " - (const unsigned char *)QString("%1").arg(m_deviceDetails->customText, -30, QChar(' ')).toUtf8().data(), - SUUNTO_VYPER_CUSTOM_TEXT_LENGHT); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->samplingRate; - rc = dc_device_write(device, SUUNTO_VYPER_SAMPLING_RATE, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->personalSafety << 2 ^ m_deviceDetails->altitude; - rc = dc_device_write(device, SUUNTO_VYPER_ALTITUDE_SAFETY, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->timeFormat; - rc = dc_device_write(device, SUUNTO_VYPER_TIMEFORMAT, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->units; - rc = dc_device_write(device, SUUNTO_VYPER_UNITS, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->diveMode; - rc = dc_device_write(device, SUUNTO_VYPER_MODEL, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->lightEnabled << 7 ^ (m_deviceDetails->light & 0x7F); - rc = dc_device_write(device, SUUNTO_VYPER_LIGHT, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->alarmDepthEnabled << 1 ^ m_deviceDetails->alarmTimeEnabled; - rc = dc_device_write(device, SUUNTO_VYPER_ALARM_DEPTH_TIME, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - // The stinger stores alarm time in seconds instead of minutes. - time = m_deviceDetails->alarmTime; - if (m_deviceDetails->model == "Stinger") - time *= 60; - data2[0] = time >> 8; - data2[1] = time & 0xFF; - rc = dc_device_write(device, SUUNTO_VYPER_ALARM_TIME, data2, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data2[0] = (int)(mm_to_feet(m_deviceDetails->alarmDepth) * 128) >> 8; - data2[1] = (int)(mm_to_feet(m_deviceDetails->alarmDepth) * 128) & 0x0FF; - rc = dc_device_write(device, SUUNTO_VYPER_ALARM_DEPTH, data2, 2); - EMIT_PROGRESS(); - return rc; -} - -#if DC_VERSION_CHECK(0, 5, 0) -static dc_status_t read_ostc3_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 57; - unsigned char hardware[1]; - - //Read hardware type - rc = hw_ostc3_device_hardware (device, hardware, sizeof (hardware)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - // FIXME: can we grab this info from libdivecomputer descriptor - // instead of hard coded here? - switch(hardware[0]) { - case OSTC3_HW_OSTC_3: - m_deviceDetails->model = "3"; - break; - case OSTC3_HW_OSTC_3P: - m_deviceDetails->model = "3+"; - break; - case OSTC3_HW_OSTC_CR: - m_deviceDetails->model = "CR"; - break; - case OSTC3_HW_OSTC_SPORT: - m_deviceDetails->model = "Sport"; - break; - case OSTC3_HW_OSTC_2: - m_deviceDetails->model = "2"; - break; - } - - //Read gas mixes - gas gas1; - gas gas2; - gas gas3; - gas gas4; - gas gas5; - unsigned char gasData[4] = { 0, 0, 0, 0 }; - - rc = hw_ostc3_device_config_read(device, OSTC3_GAS1, gasData, sizeof(gasData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - gas1.oxygen = gasData[0]; - gas1.helium = gasData[1]; - gas1.type = gasData[2]; - gas1.depth = gasData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_GAS2, gasData, sizeof(gasData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - gas2.oxygen = gasData[0]; - gas2.helium = gasData[1]; - gas2.type = gasData[2]; - gas2.depth = gasData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_GAS3, gasData, sizeof(gasData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - gas3.oxygen = gasData[0]; - gas3.helium = gasData[1]; - gas3.type = gasData[2]; - gas3.depth = gasData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_GAS4, gasData, sizeof(gasData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - gas4.oxygen = gasData[0]; - gas4.helium = gasData[1]; - gas4.type = gasData[2]; - gas4.depth = gasData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_GAS5, gasData, sizeof(gasData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - gas5.oxygen = gasData[0]; - gas5.helium = gasData[1]; - gas5.type = gasData[2]; - gas5.depth = gasData[3]; - EMIT_PROGRESS(); - - m_deviceDetails->gas1 = gas1; - m_deviceDetails->gas2 = gas2; - m_deviceDetails->gas3 = gas3; - m_deviceDetails->gas4 = gas4; - m_deviceDetails->gas5 = gas5; - EMIT_PROGRESS(); - - //Read Dil Values - gas dil1; - gas dil2; - gas dil3; - gas dil4; - gas dil5; - unsigned char dilData[4] = { 0, 0, 0, 0 }; - - rc = hw_ostc3_device_config_read(device, OSTC3_DIL1, dilData, sizeof(dilData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - dil1.oxygen = dilData[0]; - dil1.helium = dilData[1]; - dil1.type = dilData[2]; - dil1.depth = dilData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_DIL2, dilData, sizeof(dilData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - dil2.oxygen = dilData[0]; - dil2.helium = dilData[1]; - dil2.type = dilData[2]; - dil2.depth = dilData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_DIL3, dilData, sizeof(dilData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - dil3.oxygen = dilData[0]; - dil3.helium = dilData[1]; - dil3.type = dilData[2]; - dil3.depth = dilData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_DIL4, dilData, sizeof(dilData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - dil4.oxygen = dilData[0]; - dil4.helium = dilData[1]; - dil4.type = dilData[2]; - dil4.depth = dilData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_DIL5, dilData, sizeof(dilData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - dil5.oxygen = dilData[0]; - dil5.helium = dilData[1]; - dil5.type = dilData[2]; - dil5.depth = dilData[3]; - EMIT_PROGRESS(); - - m_deviceDetails->dil1 = dil1; - m_deviceDetails->dil2 = dil2; - m_deviceDetails->dil3 = dil3; - m_deviceDetails->dil4 = dil4; - m_deviceDetails->dil5 = dil5; - - //Read set point Values - setpoint sp1; - setpoint sp2; - setpoint sp3; - setpoint sp4; - setpoint sp5; - unsigned char spData[2] = { 0, 0 }; - - rc = hw_ostc3_device_config_read(device, OSTC3_SP1, spData, sizeof(spData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - sp1.sp = spData[0]; - sp1.depth = spData[1]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_SP2, spData, sizeof(spData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - sp2.sp = spData[0]; - sp2.depth = spData[1]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_SP3, spData, sizeof(spData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - sp3.sp = spData[0]; - sp3.depth = spData[1]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_SP4, spData, sizeof(spData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - sp4.sp = spData[0]; - sp4.depth = spData[1]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_SP5, spData, sizeof(spData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - sp5.sp = spData[0]; - sp5.depth = spData[1]; - EMIT_PROGRESS(); - - m_deviceDetails->sp1 = sp1; - m_deviceDetails->sp2 = sp2; - m_deviceDetails->sp3 = sp3; - m_deviceDetails->sp4 = sp4; - m_deviceDetails->sp5 = sp5; - - //Read other settings - unsigned char uData[1] = { 0 }; - -#define READ_SETTING(_OSTC3_SETTING, _DEVICE_DETAIL) \ - do { \ - rc = hw_ostc3_device_config_read(device, _OSTC3_SETTING, uData, sizeof(uData)); \ - if (rc != DC_STATUS_SUCCESS) \ - return rc; \ - m_deviceDetails->_DEVICE_DETAIL = uData[0]; \ - EMIT_PROGRESS(); \ - } while (0) - - READ_SETTING(OSTC3_DIVE_MODE, diveMode); - READ_SETTING(OSTC3_SATURATION, saturation); - READ_SETTING(OSTC3_DESATURATION, desaturation); - READ_SETTING(OSTC3_LAST_DECO, lastDeco); - READ_SETTING(OSTC3_BRIGHTNESS, brightness); - READ_SETTING(OSTC3_UNITS, units); - READ_SETTING(OSTC3_SAMPLING_RATE, samplingRate); - READ_SETTING(OSTC3_SALINITY, salinity); - READ_SETTING(OSTC3_DIVEMODE_COLOR, diveModeColor); - READ_SETTING(OSTC3_LANGUAGE, language); - READ_SETTING(OSTC3_DATE_FORMAT, dateFormat); - READ_SETTING(OSTC3_COMPASS_GAIN, compassGain); - READ_SETTING(OSTC3_SAFETY_STOP, safetyStop); - READ_SETTING(OSTC3_GF_HIGH, gfHigh); - READ_SETTING(OSTC3_GF_LOW, gfLow); - READ_SETTING(OSTC3_PPO2_MIN, ppO2Min); - READ_SETTING(OSTC3_PPO2_MAX, ppO2Max); - READ_SETTING(OSTC3_FUTURE_TTS, futureTTS); - READ_SETTING(OSTC3_CCR_MODE, ccrMode); - READ_SETTING(OSTC3_DECO_TYPE, decoType); - READ_SETTING(OSTC3_AGF_SELECTABLE, aGFSelectable); - READ_SETTING(OSTC3_AGF_HIGH, aGFHigh); - READ_SETTING(OSTC3_AGF_LOW, aGFLow); - READ_SETTING(OSTC3_CALIBRATION_GAS_O2, calibrationGas); - READ_SETTING(OSTC3_FLIP_SCREEN, flipScreen); - READ_SETTING(OSTC3_SETPOINT_FALLBACK, setPointFallback); - READ_SETTING(OSTC3_LEFT_BUTTON_SENSIVITY, leftButtonSensitivity); - READ_SETTING(OSTC3_RIGHT_BUTTON_SENSIVITY, rightButtonSensitivity); - READ_SETTING(OSTC3_BOTTOM_GAS_CONSUMPTION, bottomGasConsumption); - READ_SETTING(OSTC3_DECO_GAS_CONSUMPTION, decoGasConsumption); - READ_SETTING(OSTC3_MOD_WARNING, modWarning); - READ_SETTING(OSTC3_DYNAMIC_ASCEND_RATE, dynamicAscendRate); - READ_SETTING(OSTC3_GRAPHICAL_SPEED_INDICATOR, graphicalSpeedIndicator); - READ_SETTING(OSTC3_ALWAYS_SHOW_PPO2, alwaysShowppO2); - READ_SETTING(OSTC3_SAFETY_STOP_LENGTH, safetyStopLength); - READ_SETTING(OSTC3_SAFETY_STOP_START_DEPTH, safetyStopStartDepth); - READ_SETTING(OSTC3_SAFETY_STOP_END_DEPTH, safetyStopEndDepth); - READ_SETTING(OSTC3_SAFETY_STOP_RESET_DEPTH, safetyStopResetDepth); - -#undef READ_SETTING - - rc = hw_ostc3_device_config_read(device, OSTC3_PRESSURE_SENSOR_OFFSET, uData, sizeof(uData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - // OSTC3 stores the pressureSensorOffset in two-complement - m_deviceDetails->pressureSensorOffset = (signed char)uData[0]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_TEMP_SENSOR_OFFSET, uData, sizeof(uData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - // OSTC3 stores the tempSensorOffset in two-complement - m_deviceDetails->tempSensorOffset = (signed char)uData[0]; - EMIT_PROGRESS(); - - //read firmware settings - unsigned char fData[64] = { 0 }; - rc = hw_ostc3_device_version(device, fData, sizeof(fData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - int serial = fData[0] + (fData[1] << 8); - m_deviceDetails->serialNo = QString::number(serial); - m_deviceDetails->firmwareVersion = QString::number(fData[2]) + "." + QString::number(fData[3]); - QByteArray ar((char *)fData + 4, 60); - m_deviceDetails->customText = ar.trimmed(); - EMIT_PROGRESS(); - - return rc; -} - -static dc_status_t write_ostc3_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 56; - - //write gas values - unsigned char gas1Data[4] = { - m_deviceDetails->gas1.oxygen, - m_deviceDetails->gas1.helium, - m_deviceDetails->gas1.type, - m_deviceDetails->gas1.depth - }; - - unsigned char gas2Data[4] = { - m_deviceDetails->gas2.oxygen, - m_deviceDetails->gas2.helium, - m_deviceDetails->gas2.type, - m_deviceDetails->gas2.depth - }; - - unsigned char gas3Data[4] = { - m_deviceDetails->gas3.oxygen, - m_deviceDetails->gas3.helium, - m_deviceDetails->gas3.type, - m_deviceDetails->gas3.depth - }; - - unsigned char gas4Data[4] = { - m_deviceDetails->gas4.oxygen, - m_deviceDetails->gas4.helium, - m_deviceDetails->gas4.type, - m_deviceDetails->gas4.depth - }; - - unsigned char gas5Data[4] = { - m_deviceDetails->gas5.oxygen, - m_deviceDetails->gas5.helium, - m_deviceDetails->gas5.type, - m_deviceDetails->gas5.depth - }; - //gas 1 - rc = hw_ostc3_device_config_write(device, OSTC3_GAS1, gas1Data, sizeof(gas1Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //gas 2 - rc = hw_ostc3_device_config_write(device, OSTC3_GAS2, gas2Data, sizeof(gas2Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //gas 3 - rc = hw_ostc3_device_config_write(device, OSTC3_GAS3, gas3Data, sizeof(gas3Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //gas 4 - rc = hw_ostc3_device_config_write(device, OSTC3_GAS4, gas4Data, sizeof(gas4Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //gas 5 - rc = hw_ostc3_device_config_write(device, OSTC3_GAS5, gas5Data, sizeof(gas5Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - //write set point values - unsigned char sp1Data[2] = { - m_deviceDetails->sp1.sp, - m_deviceDetails->sp1.depth - }; - - unsigned char sp2Data[2] = { - m_deviceDetails->sp2.sp, - m_deviceDetails->sp2.depth - }; - - unsigned char sp3Data[2] = { - m_deviceDetails->sp3.sp, - m_deviceDetails->sp3.depth - }; - - unsigned char sp4Data[2] = { - m_deviceDetails->sp4.sp, - m_deviceDetails->sp4.depth - }; - - unsigned char sp5Data[2] = { - m_deviceDetails->sp5.sp, - m_deviceDetails->sp5.depth - }; - - //sp 1 - rc = hw_ostc3_device_config_write(device, OSTC3_SP1, sp1Data, sizeof(sp1Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //sp 2 - rc = hw_ostc3_device_config_write(device, OSTC3_SP2, sp2Data, sizeof(sp2Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //sp 3 - rc = hw_ostc3_device_config_write(device, OSTC3_SP3, sp3Data, sizeof(sp3Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //sp 4 - rc = hw_ostc3_device_config_write(device, OSTC3_SP4, sp4Data, sizeof(sp4Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //sp 5 - rc = hw_ostc3_device_config_write(device, OSTC3_SP5, sp5Data, sizeof(sp5Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - //write dil values - unsigned char dil1Data[4] = { - m_deviceDetails->dil1.oxygen, - m_deviceDetails->dil1.helium, - m_deviceDetails->dil1.type, - m_deviceDetails->dil1.depth - }; - - unsigned char dil2Data[4] = { - m_deviceDetails->dil2.oxygen, - m_deviceDetails->dil2.helium, - m_deviceDetails->dil2.type, - m_deviceDetails->dil2.depth - }; - - unsigned char dil3Data[4] = { - m_deviceDetails->dil3.oxygen, - m_deviceDetails->dil3.helium, - m_deviceDetails->dil3.type, - m_deviceDetails->dil3.depth - }; - - unsigned char dil4Data[4] = { - m_deviceDetails->dil4.oxygen, - m_deviceDetails->dil4.helium, - m_deviceDetails->dil4.type, - m_deviceDetails->dil4.depth - }; - - unsigned char dil5Data[4] = { - m_deviceDetails->dil5.oxygen, - m_deviceDetails->dil5.helium, - m_deviceDetails->dil5.type, - m_deviceDetails->dil5.depth - }; - //dil 1 - rc = hw_ostc3_device_config_write(device, OSTC3_DIL1, dil1Data, sizeof(gas1Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //dil 2 - rc = hw_ostc3_device_config_write(device, OSTC3_DIL2, dil2Data, sizeof(dil2Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //dil 3 - rc = hw_ostc3_device_config_write(device, OSTC3_DIL3, dil3Data, sizeof(dil3Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //dil 4 - rc = hw_ostc3_device_config_write(device, OSTC3_DIL4, dil4Data, sizeof(dil4Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //dil 5 - rc = hw_ostc3_device_config_write(device, OSTC3_DIL5, dil5Data, sizeof(dil5Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - //write general settings - //custom text - rc = hw_ostc3_device_customtext(device, m_deviceDetails->customText.toUtf8().data()); - if (rc != DC_STATUS_SUCCESS) - return rc; - - unsigned char data[1] = { 0 }; -#define WRITE_SETTING(_OSTC3_SETTING, _DEVICE_DETAIL) \ - do { \ - data[0] = m_deviceDetails->_DEVICE_DETAIL; \ - rc = hw_ostc3_device_config_write(device, _OSTC3_SETTING, data, sizeof(data)); \ - if (rc != DC_STATUS_SUCCESS) \ - return rc; \ - EMIT_PROGRESS(); \ - } while (0) - - WRITE_SETTING(OSTC3_DIVE_MODE, diveMode); - WRITE_SETTING(OSTC3_SATURATION, saturation); - WRITE_SETTING(OSTC3_DESATURATION, desaturation); - WRITE_SETTING(OSTC3_LAST_DECO, lastDeco); - WRITE_SETTING(OSTC3_BRIGHTNESS, brightness); - WRITE_SETTING(OSTC3_UNITS, units); - WRITE_SETTING(OSTC3_SAMPLING_RATE, samplingRate); - WRITE_SETTING(OSTC3_SALINITY, salinity); - WRITE_SETTING(OSTC3_DIVEMODE_COLOR, diveModeColor); - WRITE_SETTING(OSTC3_LANGUAGE, language); - WRITE_SETTING(OSTC3_DATE_FORMAT, dateFormat); - WRITE_SETTING(OSTC3_COMPASS_GAIN, compassGain); - WRITE_SETTING(OSTC3_SAFETY_STOP, safetyStop); - WRITE_SETTING(OSTC3_GF_HIGH, gfHigh); - WRITE_SETTING(OSTC3_GF_LOW, gfLow); - WRITE_SETTING(OSTC3_PPO2_MIN, ppO2Min); - WRITE_SETTING(OSTC3_PPO2_MAX, ppO2Max); - WRITE_SETTING(OSTC3_FUTURE_TTS, futureTTS); - WRITE_SETTING(OSTC3_CCR_MODE, ccrMode); - WRITE_SETTING(OSTC3_DECO_TYPE, decoType); - WRITE_SETTING(OSTC3_AGF_SELECTABLE, aGFSelectable); - WRITE_SETTING(OSTC3_AGF_HIGH, aGFHigh); - WRITE_SETTING(OSTC3_AGF_LOW, aGFLow); - WRITE_SETTING(OSTC3_CALIBRATION_GAS_O2, calibrationGas); - WRITE_SETTING(OSTC3_FLIP_SCREEN, flipScreen); - WRITE_SETTING(OSTC3_SETPOINT_FALLBACK, setPointFallback); - WRITE_SETTING(OSTC3_LEFT_BUTTON_SENSIVITY, leftButtonSensitivity); - WRITE_SETTING(OSTC3_RIGHT_BUTTON_SENSIVITY, rightButtonSensitivity); - WRITE_SETTING(OSTC3_BOTTOM_GAS_CONSUMPTION, bottomGasConsumption); - WRITE_SETTING(OSTC3_DECO_GAS_CONSUMPTION, decoGasConsumption); - WRITE_SETTING(OSTC3_MOD_WARNING, modWarning); - WRITE_SETTING(OSTC3_DYNAMIC_ASCEND_RATE, dynamicAscendRate); - WRITE_SETTING(OSTC3_GRAPHICAL_SPEED_INDICATOR, graphicalSpeedIndicator); - WRITE_SETTING(OSTC3_ALWAYS_SHOW_PPO2, alwaysShowppO2); - WRITE_SETTING(OSTC3_TEMP_SENSOR_OFFSET, tempSensorOffset); - WRITE_SETTING(OSTC3_SAFETY_STOP_LENGTH, safetyStopLength); - WRITE_SETTING(OSTC3_SAFETY_STOP_START_DEPTH, safetyStopStartDepth); - WRITE_SETTING(OSTC3_SAFETY_STOP_END_DEPTH, safetyStopEndDepth); - WRITE_SETTING(OSTC3_SAFETY_STOP_RESET_DEPTH, safetyStopResetDepth); - -#undef WRITE_SETTING - - // OSTC3 stores the pressureSensorOffset in two-complement - data[0] = (unsigned char)m_deviceDetails->pressureSensorOffset; - rc = hw_ostc3_device_config_write(device, OSTC3_PRESSURE_SENSOR_OFFSET, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - // OSTC3 stores the tempSensorOffset in two-complement - data[0] = (unsigned char)m_deviceDetails->pressureSensorOffset; - rc = hw_ostc3_device_config_write(device, OSTC3_TEMP_SENSOR_OFFSET, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - //sync date and time - if (m_deviceDetails->syncTime) { - dc_datetime_t now; - dc_datetime_localtime(&now, dc_datetime_now()); - - rc = hw_ostc3_device_clock(device, &now); - } - EMIT_PROGRESS(); - - return rc; -} -#endif /* DC_VERSION_CHECK(0, 5, 0) */ - -static dc_status_t read_ostc_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 3; - - unsigned char data[256] = {}; -#ifdef DEBUG_OSTC_CF - // FIXME: how should we report settings not supported back? - unsigned char max_CF = 0; -#endif - rc = hw_ostc_device_eeprom_read(device, 0, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - m_deviceDetails->serialNo = QString::number(data[1] << 8 ^ data[0]); - m_deviceDetails->numberOfDives = data[3] << 8 ^ data[2]; - //Byte5-6: - //Gas 1 default (%O2=21, %He=0) - gas gas1; - gas1.oxygen = data[6]; - gas1.helium = data[7]; - //Byte9-10: - //Gas 2 default (%O2=21, %He=0) - gas gas2; - gas2.oxygen = data[10]; - gas2.helium = data[11]; - //Byte13-14: - //Gas 3 default (%O2=21, %He=0) - gas gas3; - gas3.oxygen = data[14]; - gas3.helium = data[15]; - //Byte17-18: - //Gas 4 default (%O2=21, %He=0) - gas gas4; - gas4.oxygen = data[18]; - gas4.helium = data[19]; - //Byte21-22: - //Gas 5 default (%O2=21, %He=0) - gas gas5; - gas5.oxygen = data[22]; - gas5.helium = data[23]; - //Byte25-26: - //Gas 6 current (%O2, %He) - m_deviceDetails->salinity = data[26]; - // Active Gas Flag Register - gas1.type = data[27] & 0x01; - gas2.type = (data[27] & 0x02) >> 1; - gas3.type = (data[27] & 0x04) >> 2; - gas4.type = (data[27] & 0x08) >> 3; - gas5.type = (data[27] & 0x10) >> 4; - - // Gas switch depths - gas1.depth = data[28]; - gas2.depth = data[29]; - gas3.depth = data[30]; - gas4.depth = data[31]; - gas5.depth = data[32]; - // 33 which gas is Fist gas - switch (data[33]) { - case 1: - gas1.type = 2; - break; - case 2: - gas2.type = 2; - break; - case 3: - gas3.type = 2; - break; - case 4: - gas4.type = 2; - break; - case 5: - gas5.type = 2; - break; - default: - //Error? - break; - } - // Data filled up, set the gases. - m_deviceDetails->gas1 = gas1; - m_deviceDetails->gas2 = gas2; - m_deviceDetails->gas3 = gas3; - m_deviceDetails->gas4 = gas4; - m_deviceDetails->gas5 = gas5; - m_deviceDetails->decoType = data[34]; - //Byte36: - //Use O2 Sensor Module in CC Modes (0= OFF, 1= ON) (Only available in old OSTC1 - unused for OSTC Mk.2/2N) - //m_deviceDetails->ccrMode = data[35]; - setpoint sp1; - sp1.sp = data[36]; - sp1.depth = 0; - setpoint sp2; - sp2.sp = data[37]; - sp2.depth = 0; - setpoint sp3; - sp3.sp = data[38]; - sp3.depth = 0; - m_deviceDetails->sp1 = sp1; - m_deviceDetails->sp2 = sp2; - m_deviceDetails->sp3 = sp3; - // Byte41-42: - // Lowest Battery voltage seen (in mV) - // Byte43: - // Lowest Battery voltage seen at (Month) - // Byte44: - // Lowest Battery voltage seen at (Day) - // Byte45: - // Lowest Battery voltage seen at (Year) - // Byte46-47: - // Lowest Battery voltage seen at (Temperature in 0.1 °C) - // Byte48: - // Last complete charge at (Month) - // Byte49: - // Last complete charge at (Day) - // Byte50: - // Last complete charge at (Year) - // Byte51-52: - // Total charge cycles - // Byte53-54: - // Total complete charge cycles - // Byte55-56: - // Temperature Extrema minimum (Temperature in 0.1 °C) - // Byte57: - // Temperature Extrema minimum at (Month) - // Byte58: - // Temperature Extrema minimum at (Day) - // Byte59: - // Temperature Extrema minimum at (Year) - // Byte60-61: - // Temperature Extrema maximum (Temperature in 0.1 °C) - // Byte62: - // Temperature Extrema maximum at (Month) - // Byte63: - // Temperature Extrema maximum at (Day) - // Byte64: - // Temperature Extrema maximum at (Year) - // Byte65: - // Custom Text active (=1), Custom Text Disabled (<>1) - // Byte66-90: - // TO FIX EDITOR SYNTAX/INDENT { - // (25Bytes): Custom Text for Surfacemode (Real text must end with "}") - // Example: OSTC Dive Computer} (19 Characters incl. "}") Bytes 85-90 will be ignored. - if (data[64] == 1) { - // Make shure the data is null-terminated - data[89] = 0; - // Find the internal termination and replace it with 0 - char *term = strchr((char *)data + 65, (int)'}'); - if (term) - *term = 0; - m_deviceDetails->customText = (const char *)data + 65; - } - // Byte91: - // Dim OLED in Divemode (>0), Normal mode (=0) - // Byte92: - // Date format for all outputs: - // =0: MM/DD/YY - // =1: DD/MM/YY - // =2: YY/MM/DD - m_deviceDetails->dateFormat = data[91]; -// Byte93: -// Total number of CF used in installed firmware -#ifdef DEBUG_OSTC_CF - max_CF = data[92]; -#endif - // Byte94: - // Last selected view for customview area in surface mode - // Byte95: - // Last selected view for customview area in dive mode - // Byte96-97: - // Diluent 1 Default (%O2,%He) - // Byte98-99: - // Diluent 1 Current (%O2,%He) - gas dil1(data[97], data[98]); - // Byte100-101: - // Gasuent 2 Default (%O2,%He) - // Byte102-103: - // Gasuent 2 Current (%O2,%He) - gas dil2(data[101], data[102]); - // Byte104-105: - // Gasuent 3 Default (%O2,%He) - // Byte106-107: - // Gasuent 3 Current (%O2,%He) - gas dil3(data[105], data[106]); - // Byte108-109: - // Gasuent 4 Default (%O2,%He) - // Byte110-111: - // Gasuent 4 Current (%O2,%He) - gas dil4(data[109], data[110]); - // Byte112-113: - // Gasuent 5 Default (%O2,%He) - // Byte114-115: - // Gasuent 5 Current (%O2,%He) - gas dil5(data[113], data[114]); - // Byte116: - // First Diluent (1-5) - switch (data[115]) { - case 1: - dil1.type = 2; - break; - case 2: - dil2.type = 2; - break; - case 3: - dil3.type = 2; - break; - case 4: - dil4.type = 2; - break; - case 5: - dil5.type = 2; - break; - default: - //Error? - break; - } - m_deviceDetails->dil1 = dil1; - m_deviceDetails->dil2 = dil2; - m_deviceDetails->dil3 = dil3; - m_deviceDetails->dil4 = dil4; - m_deviceDetails->dil5 = dil5; - // Byte117-128: - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF0-CF31) - - // Decode the relevant ones - // CF11: Factor for saturation processes - m_deviceDetails->saturation = read_ostc_cf(data, 11); - // CF12: Factor for desaturation processes - m_deviceDetails->desaturation = read_ostc_cf(data, 12); - // CF17: Lower threshold for ppO2 warning - m_deviceDetails->ppO2Min = read_ostc_cf(data, 17); - // CF18: Upper threshold for ppO2 warning - m_deviceDetails->ppO2Max = read_ostc_cf(data, 18); - // CF20: Depth sampling rate for Profile storage - m_deviceDetails->samplingRate = read_ostc_cf(data, 20); - // CF29: Depth of last decompression stop - m_deviceDetails->lastDeco = read_ostc_cf(data, 29); - -#ifdef DEBUG_OSTC_CF - for (int cf = 0; cf <= 31 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - - rc = hw_ostc_device_eeprom_read(device, 1, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - // Byte1: - // Logbook version indicator (Not writable!) - // Byte2-3: - // Last Firmware installed, 1st Byte.2nd Byte (e.g. „1.90“) (Not writable!) - m_deviceDetails->firmwareVersion = QString::number(data[1]) + "." + QString::number(data[2]); - // Byte4: - // OLED brightness (=0: Eco, =1 High) (Not writable!) - // Byte5-11: - // Time/Date vault during firmware updates - // Byte12-128 - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF 32-63) - - // Decode the relevant ones - // CF32: Gradient Factor low - m_deviceDetails->gfLow = read_ostc_cf(data, 32); - // CF33: Gradient Factor high - m_deviceDetails->gfHigh = read_ostc_cf(data, 33); - // CF56: Bottom gas consumption - m_deviceDetails->bottomGasConsumption = read_ostc_cf(data, 56); - // CF57: Ascent gas consumption - m_deviceDetails->decoGasConsumption = read_ostc_cf(data, 57); - // CF58: Future time to surface setFutureTTS - m_deviceDetails->futureTTS = read_ostc_cf(data, 58); - -#ifdef DEBUG_OSTC_CF - for (int cf = 32; cf <= 63 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - - rc = hw_ostc_device_eeprom_read(device, 2, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - // Byte1-4: - // not used/reserved (Not writable!) - // Byte5-128: - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF 64-95) - - // Decode the relevant ones - // CF60: Graphic velocity - m_deviceDetails->graphicalSpeedIndicator = read_ostc_cf(data, 60); - // CF65: Show safety stop - m_deviceDetails->safetyStop = read_ostc_cf(data, 65); - // CF67: Alternaitve Gradient Factor low - m_deviceDetails->aGFLow = read_ostc_cf(data, 67); - // CF68: Alternative Gradient Factor high - m_deviceDetails->aGFHigh = read_ostc_cf(data, 68); - // CF69: Allow Gradient Factor change - m_deviceDetails->aGFSelectable = read_ostc_cf(data, 69); - // CF70: Safety Stop Duration [s] - m_deviceDetails->safetyStopLength = read_ostc_cf(data, 70); - // CF71: Safety Stop Start Depth [m] - m_deviceDetails->safetyStopStartDepth = read_ostc_cf(data, 71); - // CF72: Safety Stop End Depth [m] - m_deviceDetails->safetyStopEndDepth = read_ostc_cf(data, 72); - // CF73: Safety Stop Reset Depth [m] - m_deviceDetails->safetyStopResetDepth = read_ostc_cf(data, 73); - // CF74: Battery Timeout [min] - -#ifdef DEBUG_OSTC_CF - for (int cf = 64; cf <= 95 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - - return rc; -} - -static dc_status_t write_ostc_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 7; - unsigned char data[256] = {}; - unsigned char max_CF = 0; - - // Because we write whole memory blocks, we read all the current - // values out and then change then ones we should change. - rc = hw_ostc_device_eeprom_read(device, 0, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //Byte5-6: - //Gas 1 default (%O2=21, %He=0) - gas gas1 = m_deviceDetails->gas1; - data[6] = gas1.oxygen; - data[7] = gas1.helium; - //Byte9-10: - //Gas 2 default (%O2=21, %He=0) - gas gas2 = m_deviceDetails->gas2; - data[10] = gas2.oxygen; - data[11] = gas2.helium; - //Byte13-14: - //Gas 3 default (%O2=21, %He=0) - gas gas3 = m_deviceDetails->gas3; - data[14] = gas3.oxygen; - data[15] = gas3.helium; - //Byte17-18: - //Gas 4 default (%O2=21, %He=0) - gas gas4 = m_deviceDetails->gas4; - data[18] = gas4.oxygen; - data[19] = gas4.helium; - //Byte21-22: - //Gas 5 default (%O2=21, %He=0) - gas gas5 = m_deviceDetails->gas5; - data[22] = gas5.oxygen; - data[23] = gas5.helium; - //Byte25-26: - //Gas 6 current (%O2, %He) - data[26] = m_deviceDetails->salinity; - // Gas types, 0=Disabled, 1=Active, 2=Fist - // Active Gas Flag Register - data[27] = 0; - if (gas1.type) - data[27] ^= 0x01; - if (gas2.type) - data[27] ^= 0x02; - if (gas3.type) - data[27] ^= 0x04; - if (gas4.type) - data[27] ^= 0x08; - if (gas5.type) - data[27] ^= 0x10; - - // Gas switch depths - data[28] = gas1.depth; - data[29] = gas2.depth; - data[30] = gas3.depth; - data[31] = gas4.depth; - data[32] = gas5.depth; - // 33 which gas is Fist gas - if (gas1.type == 2) - data[33] = 1; - else if (gas2.type == 2) - data[33] = 2; - else if (gas3.type == 2) - data[33] = 3; - else if (gas4.type == 2) - data[33] = 4; - else if (gas5.type == 2) - data[33] = 5; - else - // FIXME: No gas was First? - // Set gas 1 to first - data[33] = 1; - - data[34] = m_deviceDetails->decoType; - //Byte36: - //Use O2 Sensor Module in CC Modes (0= OFF, 1= ON) (Only available in old OSTC1 - unused for OSTC Mk.2/2N) - //m_deviceDetails->ccrMode = data[35]; - data[36] = m_deviceDetails->sp1.sp; - data[37] = m_deviceDetails->sp2.sp; - data[38] = m_deviceDetails->sp3.sp; - // Byte41-42: - // Lowest Battery voltage seen (in mV) - // Byte43: - // Lowest Battery voltage seen at (Month) - // Byte44: - // Lowest Battery voltage seen at (Day) - // Byte45: - // Lowest Battery voltage seen at (Year) - // Byte46-47: - // Lowest Battery voltage seen at (Temperature in 0.1 °C) - // Byte48: - // Last complete charge at (Month) - // Byte49: - // Last complete charge at (Day) - // Byte50: - // Last complete charge at (Year) - // Byte51-52: - // Total charge cycles - // Byte53-54: - // Total complete charge cycles - // Byte55-56: - // Temperature Extrema minimum (Temperature in 0.1 °C) - // Byte57: - // Temperature Extrema minimum at (Month) - // Byte58: - // Temperature Extrema minimum at (Day) - // Byte59: - // Temperature Extrema minimum at (Year) - // Byte60-61: - // Temperature Extrema maximum (Temperature in 0.1 °C) - // Byte62: - // Temperature Extrema maximum at (Month) - // Byte63: - // Temperature Extrema maximum at (Day) - // Byte64: - // Temperature Extrema maximum at (Year) - // Byte65: - // Custom Text active (=1), Custom Text Disabled (<>1) - // Byte66-90: - // (25Bytes): Custom Text for Surfacemode (Real text must end with "}") - // Example: "OSTC Dive Computer}" (19 Characters incl. "}") Bytes 85-90 will be ignored. - if (m_deviceDetails->customText == "") { - data[64] = 0; - } else { - data[64] = 1; - // Copy the string to the right place in the memory, padded with 0x20 (" ") - strncpy((char *)data + 65, QString("%1").arg(m_deviceDetails->customText, -23, QChar(' ')).toUtf8().data(), 23); - // And terminate the string. - if (m_deviceDetails->customText.length() <= 23) - data[65 + m_deviceDetails->customText.length()] = '}'; - else - data[90] = '}'; - } - // Byte91: - // Dim OLED in Divemode (>0), Normal mode (=0) - // Byte92: - // Date format for all outputs: - // =0: MM/DD/YY - // =1: DD/MM/YY - // =2: YY/MM/DD - data[91] = m_deviceDetails->dateFormat; - // Byte93: - // Total number of CF used in installed firmware - max_CF = data[92]; - // Byte94: - // Last selected view for customview area in surface mode - // Byte95: - // Last selected view for customview area in dive mode - // Byte96-97: - // Diluent 1 Default (%O2,%He) - // Byte98-99: - // Diluent 1 Current (%O2,%He) - gas dil1 = m_deviceDetails->dil1; - data[97] = dil1.oxygen; - data[98] = dil1.helium; - // Byte100-101: - // Gasuent 2 Default (%O2,%He) - // Byte102-103: - // Gasuent 2 Current (%O2,%He) - gas dil2 = m_deviceDetails->dil2; - data[101] = dil2.oxygen; - data[102] = dil2.helium; - // Byte104-105: - // Gasuent 3 Default (%O2,%He) - // Byte106-107: - // Gasuent 3 Current (%O2,%He) - gas dil3 = m_deviceDetails->dil3; - data[105] = dil3.oxygen; - data[106] = dil3.helium; - // Byte108-109: - // Gasuent 4 Default (%O2,%He) - // Byte110-111: - // Gasuent 4 Current (%O2,%He) - gas dil4 = m_deviceDetails->dil4; - data[109] = dil4.oxygen; - data[110] = dil4.helium; - // Byte112-113: - // Gasuent 5 Default (%O2,%He) - // Byte114-115: - // Gasuent 5 Current (%O2,%He) - gas dil5 = m_deviceDetails->dil5; - data[113] = dil5.oxygen; - data[114] = dil5.helium; - // Byte116: - // First Diluent (1-5) - if (dil1.type == 2) - data[115] = 1; - else if (dil2.type == 2) - data[115] = 2; - else if (dil3.type == 2) - data[115] = 3; - else if (dil4.type == 2) - data[115] = 4; - else if (dil5.type == 2) - data[115] = 5; - else - // FIXME: No first diluent? - // Set gas 1 to fist - data[115] = 1; - - // Byte117-128: - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF0-CF31) - - // Write the relevant ones - // CF11: Factor for saturation processes - write_ostc_cf(data, 11, max_CF, m_deviceDetails->saturation); - // CF12: Factor for desaturation processes - write_ostc_cf(data, 12, max_CF, m_deviceDetails->desaturation); - // CF17: Lower threshold for ppO2 warning - write_ostc_cf(data, 17, max_CF, m_deviceDetails->ppO2Min); - // CF18: Upper threshold for ppO2 warning - write_ostc_cf(data, 18, max_CF, m_deviceDetails->ppO2Max); - // CF20: Depth sampling rate for Profile storage - write_ostc_cf(data, 20, max_CF, m_deviceDetails->samplingRate); - // CF29: Depth of last decompression stop - write_ostc_cf(data, 29, max_CF, m_deviceDetails->lastDeco); - -#ifdef DEBUG_OSTC_CF - for (int cf = 0; cf <= 31 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - rc = hw_ostc_device_eeprom_write(device, 0, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - rc = hw_ostc_device_eeprom_read(device, 1, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - // Byte1: - // Logbook version indicator (Not writable!) - // Byte2-3: - // Last Firmware installed, 1st Byte.2nd Byte (e.g. „1.90“) (Not writable!) - // Byte4: - // OLED brightness (=0: Eco, =1 High) (Not writable!) - // Byte5-11: - // Time/Date vault during firmware updates - // Byte12-128 - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF 32-63) - - // Decode the relevant ones - // CF32: Gradient Factor low - write_ostc_cf(data, 32, max_CF, m_deviceDetails->gfLow); - // CF33: Gradient Factor high - write_ostc_cf(data, 33, max_CF, m_deviceDetails->gfHigh); - // CF56: Bottom gas consumption - write_ostc_cf(data, 56, max_CF, m_deviceDetails->bottomGasConsumption); - // CF57: Ascent gas consumption - write_ostc_cf(data, 57, max_CF, m_deviceDetails->decoGasConsumption); - // CF58: Future time to surface setFutureTTS - write_ostc_cf(data, 58, max_CF, m_deviceDetails->futureTTS); -#ifdef DEBUG_OSTC_CF - for (int cf = 32; cf <= 63 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - rc = hw_ostc_device_eeprom_write(device, 1, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - rc = hw_ostc_device_eeprom_read(device, 2, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - // Byte1-4: - // not used/reserved (Not writable!) - // Byte5-128: - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF 64-95) - - // Decode the relevant ones - // CF60: Graphic velocity - write_ostc_cf(data, 60, max_CF, m_deviceDetails->graphicalSpeedIndicator); - // CF65: Show safety stop - write_ostc_cf(data, 65, max_CF, m_deviceDetails->safetyStop); - // CF67: Alternaitve Gradient Factor low - write_ostc_cf(data, 67, max_CF, m_deviceDetails->aGFLow); - // CF68: Alternative Gradient Factor high - write_ostc_cf(data, 68, max_CF, m_deviceDetails->aGFHigh); - // CF69: Allow Gradient Factor change - write_ostc_cf(data, 69, max_CF, m_deviceDetails->aGFSelectable); - // CF70: Safety Stop Duration [s] - write_ostc_cf(data, 70, max_CF, m_deviceDetails->safetyStopLength); - // CF71: Safety Stop Start Depth [m] - write_ostc_cf(data, 71, max_CF, m_deviceDetails->safetyStopStartDepth); - // CF72: Safety Stop End Depth [m] - write_ostc_cf(data, 72, max_CF, m_deviceDetails->safetyStopEndDepth); - // CF73: Safety Stop Reset Depth [m] - write_ostc_cf(data, 73, max_CF, m_deviceDetails->safetyStopResetDepth); - // CF74: Battery Timeout [min] - -#ifdef DEBUG_OSTC_CF - for (int cf = 64; cf <= 95 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - rc = hw_ostc_device_eeprom_write(device, 2, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - //sync date and time - if (m_deviceDetails->syncTime) { - QDateTime timeToSet = QDateTime::currentDateTime(); - dc_datetime_t time; - time.year = timeToSet.date().year(); - time.month = timeToSet.date().month(); - time.day = timeToSet.date().day(); - time.hour = timeToSet.time().hour(); - time.minute = timeToSet.time().minute(); - time.second = timeToSet.time().second(); - rc = hw_ostc_device_clock(device, &time); - } - EMIT_PROGRESS(); - return rc; -} - -#undef EMIT_PROGRESS - -DeviceThread::DeviceThread(QObject *parent, device_data_t *data) : QThread(parent), m_data(data) -{ -} - -void DeviceThread::progressCB(int percent) -{ - emit progress(percent); -} - -void DeviceThread::event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata) -{ - Q_UNUSED(device); - - const dc_event_progress_t *progress = (dc_event_progress_t *) data; - DeviceThread *dt = static_cast<DeviceThread*>(userdata); - - switch (event) { - case DC_EVENT_PROGRESS: - dt->progressCB(100.0 * (double)progress->current / (double)progress->maximum); - break; - default: - emit dt->error("Unexpected event recived"); - break; - } -} - -ReadSettingsThread::ReadSettingsThread(QObject *parent, device_data_t *data) : DeviceThread(parent, data) -{ -} - -void ReadSettingsThread::run() -{ - dc_status_t rc; - - DeviceDetails *m_deviceDetails = new DeviceDetails(0); - switch (dc_device_get_type(m_data->device)) { - case DC_FAMILY_SUUNTO_VYPER: - rc = read_suunto_vyper_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc == DC_STATUS_SUCCESS) { - emit devicedetails(m_deviceDetails); - } else if (rc == DC_STATUS_UNSUPPORTED) { - emit error(tr("This feature is not yet available for the selected dive computer.")); - } else { - emit error("Failed!"); - } - break; -#if DC_VERSION_CHECK(0, 5, 0) - case DC_FAMILY_HW_OSTC3: - rc = read_ostc3_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc == DC_STATUS_SUCCESS) - emit devicedetails(m_deviceDetails); - else - emit error("Failed!"); - break; -#endif // divecomputer 0.5.0 -#ifdef DEBUG_OSTC - case DC_FAMILY_NULL: -#endif - case DC_FAMILY_HW_OSTC: - rc = read_ostc_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc == DC_STATUS_SUCCESS) - emit devicedetails(m_deviceDetails); - else - emit error("Failed!"); - break; - default: - emit error(tr("This feature is not yet available for the selected dive computer.")); - break; - } -} - -WriteSettingsThread::WriteSettingsThread(QObject *parent, device_data_t *data) : - DeviceThread(parent, data), - m_deviceDetails(NULL) -{ -} - -void WriteSettingsThread::setDeviceDetails(DeviceDetails *details) -{ - m_deviceDetails = details; -} - -void WriteSettingsThread::run() -{ - dc_status_t rc; - - switch (dc_device_get_type(m_data->device)) { - case DC_FAMILY_SUUNTO_VYPER: - rc = write_suunto_vyper_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc == DC_STATUS_UNSUPPORTED) { - emit error(tr("This feature is not yet available for the selected dive computer.")); - } else if (rc != DC_STATUS_SUCCESS) { - emit error(tr("Failed!")); - } - break; -#if DC_VERSION_CHECK(0, 5, 0) - case DC_FAMILY_HW_OSTC3: - rc = write_ostc3_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc != DC_STATUS_SUCCESS) - emit error(tr("Failed!")); - break; -#endif // divecomputer 0.5.0 -#ifdef DEBUG_OSTC - case DC_FAMILY_NULL: -#endif - case DC_FAMILY_HW_OSTC: - rc = write_ostc_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc != DC_STATUS_SUCCESS) - emit error(tr("Failed!")); - break; - default: - emit error(tr("This feature is not yet available for the selected dive computer.")); - break; - } -} - - -FirmwareUpdateThread::FirmwareUpdateThread(QObject *parent, device_data_t *data, QString fileName) : DeviceThread(parent, data), m_fileName(fileName) -{ -} - -void FirmwareUpdateThread::run() -{ - dc_status_t rc; - - rc = dc_device_set_events(m_data->device, DC_EVENT_PROGRESS, DeviceThread::event_cb, this); - if (rc != DC_STATUS_SUCCESS) { - emit error("Error registering the event handler."); - return; - } - switch (dc_device_get_type(m_data->device)) { -#if DC_VERSION_CHECK(0, 5, 0) - case DC_FAMILY_HW_OSTC3: - rc = hw_ostc3_device_fwupdate(m_data->device, m_fileName.toUtf8().data()); - break; - case DC_FAMILY_HW_OSTC: - rc = hw_ostc_device_fwupdate(m_data->device, m_fileName.toUtf8().data()); - break; -#endif // divecomputer 0.5.0 - default: - emit error(tr("This feature is not yet available for the selected dive computer.")); - return; - } - - if (rc != DC_STATUS_SUCCESS) { - emit error(tr("Firmware update failed!")); - } -} - - -ResetSettingsThread::ResetSettingsThread(QObject *parent, device_data_t *data) : DeviceThread(parent, data) -{ -} - -void ResetSettingsThread::run() -{ - dc_status_t rc = DC_STATUS_SUCCESS; - -#if DC_VERSION_CHECK(0, 5, 0) - if (dc_device_get_type(m_data->device) == DC_FAMILY_HW_OSTC3) { - rc = hw_ostc3_device_config_reset(m_data->device); - emit progress(100); - } -#endif // divecomputer 0.5.0 - if (rc != DC_STATUS_SUCCESS) { - emit error(tr("Reset settings failed!")); - } -} diff --git a/subsurface-core/configuredivecomputerthreads.h b/subsurface-core/configuredivecomputerthreads.h deleted file mode 100644 index 8817d848a..000000000 --- a/subsurface-core/configuredivecomputerthreads.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef CONFIGUREDIVECOMPUTERTHREADS_H -#define CONFIGUREDIVECOMPUTERTHREADS_H - -#include <QObject> -#include <QThread> -#include "libdivecomputer.h" -#include "devicedetails.h" - -class DeviceThread : public QThread { - Q_OBJECT -public: - DeviceThread(QObject *parent, device_data_t *data); - virtual void run() = 0; -signals: - void error(QString err); - void progress(int value); -protected: - device_data_t *m_data; - void progressCB(int value); - static void event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata); -}; - -class ReadSettingsThread : public DeviceThread { - Q_OBJECT -public: - ReadSettingsThread(QObject *parent, device_data_t *data); - void run(); -signals: - void devicedetails(DeviceDetails *newDeviceDetails); -}; - -class WriteSettingsThread : public DeviceThread { - Q_OBJECT -public: - WriteSettingsThread(QObject *parent, device_data_t *data); - void setDeviceDetails(DeviceDetails *details); - void run(); - -private: - DeviceDetails *m_deviceDetails; -}; - -class FirmwareUpdateThread : public DeviceThread { - Q_OBJECT -public: - FirmwareUpdateThread(QObject *parent, device_data_t *data, QString fileName); - void run(); - -private: - QString m_fileName; -}; - -class ResetSettingsThread : public DeviceThread { - Q_OBJECT -public: - ResetSettingsThread(QObject *parent, device_data_t *data); - void run(); -}; - -#endif // CONFIGUREDIVECOMPUTERTHREADS_H diff --git a/subsurface-core/datatrak.c b/subsurface-core/datatrak.c deleted file mode 100644 index 204ebd9b3..000000000 --- a/subsurface-core/datatrak.c +++ /dev/null @@ -1,698 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <time.h> - -#include "datatrak.h" -#include "dive.h" -#include "units.h" -#include "device.h" -#include "gettext.h" - -extern struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc); - -unsigned char lector_bytes[2], lector_word[4], tmp_1byte, *byte; -unsigned int tmp_2bytes; -char is_nitrox, is_O2, is_SCR; -unsigned long tmp_4bytes; - -static unsigned int two_bytes_to_int(unsigned char x, unsigned char y) -{ - return (x << 8) + y; -} - -static unsigned long four_bytes_to_long(unsigned char x, unsigned char y, unsigned char z, unsigned char t) -{ - return ((long)x << 24) + ((long)y << 16) + ((long)z << 8) + (long)t; -} - -static unsigned char *byte_to_bits(unsigned char byte) -{ - unsigned char i, *bits = (unsigned char *)malloc(8); - - for (i = 0; i < 8; i++) - bits[i] = byte & (1 << i); - return bits; -} - -/* - * Datatrak stores the date in days since 01-01-1600, while Subsurface uses - * time_t (seconds since 00:00 01-01-1970). Function subtracts - * (1970 - 1600) * 365,2425 = 135139,725 to our date variable, getting the - * days since Epoch. - */ -static time_t date_time_to_ssrfc(unsigned long date, int time) -{ - time_t tmp; - tmp = (date - 135140) * 86400 + time * 60; - return tmp; -} - -static unsigned char to_8859(unsigned char char_cp850) -{ - static const unsigned char char_8859[46] = { 0xc7, 0xfc, 0xe9, 0xe2, 0xe4, 0xe0, 0xe5, 0xe7, - 0xea, 0xeb, 0xe8, 0xef, 0xee, 0xec, 0xc4, 0xc5, - 0xc9, 0xe6, 0xc6, 0xf4, 0xf6, 0xf2, 0xfb, 0xf9, - 0xff, 0xd6, 0xdc, 0xf8, 0xa3, 0xd8, 0xd7, 0x66, - 0xe1, 0xed, 0xf3, 0xfa, 0xf1, 0xd1, 0xaa, 0xba, - 0xbf, 0xae, 0xac, 0xbd, 0xbc, 0xa1 }; - return char_8859[char_cp850 - 0x80]; -} - -static char *to_utf8(unsigned char *in_string) -{ - int outlen, inlen, i = 0, j = 0; - inlen = strlen((char *)in_string); - outlen = inlen * 2 + 1; - - char *out_string = calloc(outlen, 1); - for (i = 0; i < inlen; i++) { - if (in_string[i] < 127) - out_string[j] = in_string[i]; - else { - if (in_string[i] > 127 && in_string[i] <= 173) - in_string[i] = to_8859(in_string[i]); - out_string[j] = (in_string[i] >> 6) | 0xC0; - j++; - out_string[j] = (in_string[i] & 0x3F) | 0x80; - } - j++; - } - out_string[j + 1] = '\0'; - return out_string; -} - -/* - * Subsurface sample structure doesn't support the flags and alarms in the dt .log - * so will treat them as dc events. - */ -static struct sample *dtrak_profile(struct dive *dt_dive, FILE *archivo) -{ - int i, j = 1, interval, o2percent = dt_dive->cylinder[0].gasmix.o2.permille / 10; - struct sample *sample = dt_dive->dc.sample; - struct divecomputer *dc = &dt_dive->dc; - - for (i = 1; i <= dt_dive->dc.alloc_samples; i++) { - if (fread(&lector_bytes, 1, 2, archivo) != 2) - return sample; - interval= 20 * (i + 1); - sample = add_sample(sample, interval, dc); - sample->depth.mm = (two_bytes_to_int(lector_bytes[0], lector_bytes[1]) & 0xFFC0) * 1000 / 410; - byte = byte_to_bits(two_bytes_to_int(lector_bytes[0], lector_bytes[1]) & 0x003F); - if (byte[0] != 0) - sample->in_deco = true; - else - sample->in_deco = false; - if (byte[1] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, "rbt"); - if (byte[2] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, "ascent"); - if (byte[3] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, "ceiling"); - if (byte[4] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, "workload"); - if (byte[5] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, "transmitter"); - if (j == 3) { - read_bytes(1); - if (is_O2) { - read_bytes(1); - o2percent = tmp_1byte; - } - j = 0; - } - free(byte); - - // In commit 5f44fdd setpoint replaced po2, so although this is not necessarily CCR dive ... - if (is_O2) - sample->setpoint.mbar = calculate_depth_to_mbar(sample->depth.mm, dt_dive->surface_pressure, 0) * o2percent / 100; - j++; - } -bail: - return sample; -} - -/* - * Reads the header of a file and returns the header struct - * If it's not a DATATRAK file returns header zero initalized - */ -static dtrakheader read_file_header(FILE *archivo) -{ - dtrakheader fileheader = { 0 }; - const short headerbytes = 12; - unsigned char *lector = (unsigned char *)malloc(headerbytes); - - if (fread(lector, 1, headerbytes, archivo) != headerbytes) { - free(lector); - return fileheader; - } - if (two_bytes_to_int(lector[0], lector[1]) != 0xA100) { - report_error(translate("gettextFromC", "Error: the file does not appear to be a DATATRAK divelog")); - free(lector); - return fileheader; - } - fileheader.header = (lector[0] << 8) + lector[1]; - fileheader.dc_serial_1 = two_bytes_to_int(lector[2], lector[3]); - fileheader.dc_serial_2 = two_bytes_to_int(lector[4], lector[5]); - fileheader.divesNum = two_bytes_to_int(lector[7], lector[6]); - free(lector); - return fileheader; -} - -#define CHECK(_func, _val) if ((_func) != (_val)) goto bail - -/* - * Parses the dive extracting its data and filling a subsurface's dive structure - */ -bool dt_dive_parser(FILE *archivo, struct dive *dt_dive) -{ - unsigned char n; - int profile_length; - char *tmp_notes_str = NULL; - unsigned char *tmp_string1 = NULL, - *locality = NULL, - *dive_point = NULL; - char buffer[1024]; - struct divecomputer *dc = &dt_dive->dc; - - is_nitrox = is_O2 = is_SCR = 0; - - /* - * Parse byte to byte till next dive entry - */ - n = 0; - CHECK(fread(&lector_bytes[n], 1, 1, archivo), 1); - while (lector_bytes[n] != 0xA0) - CHECK(fread(&lector_bytes[n], 1, 1, archivo), 1); - - /* - * Found dive header 0xA000, verify second byte - */ - CHECK(fread(&lector_bytes[n+1], 1, 1, archivo), 1); - if (two_bytes_to_int(lector_bytes[0], lector_bytes[1]) != 0xA000) { - printf("Error: byte = %4x\n", two_bytes_to_int(lector_bytes[0], lector_bytes[1])); - return false; - } - - /* - * Begin parsing - * First, Date of dive, 4 bytes - */ - read_bytes(4); - - - /* - * Next, Time in minutes since 00:00 - */ - read_bytes(2); - - dt_dive->dc.when = dt_dive->when = (timestamp_t)date_time_to_ssrfc(tmp_4bytes, tmp_2bytes); - - /* - * Now, Locality, 1st byte is long of string, rest is string - */ - read_bytes(1); - read_string(locality); - - /* - * Next, Dive point, defined as Locality - */ - read_bytes(1); - read_string(dive_point); - - /* - * Subsurface only have a location variable, so we have to merge DTrak's - * Locality and Dive points. - */ - snprintf(buffer, sizeof(buffer), "%s, %s", locality, dive_point); - dt_dive->dive_site_uuid = get_dive_site_uuid_by_name(buffer, NULL); - if (dt_dive->dive_site_uuid == 0) - dt_dive->dive_site_uuid = create_dive_site(buffer, dt_dive->when); - free(locality); - free(dive_point); - - /* - * Altitude. Don't exist in Subsurface, the equivalent would be - * surface air pressure which can, be calculated from altitude. - * As dtrak registers altitude intervals, we, arbitrarily, choose - * the lower altitude/pressure equivalence for each segment. So - * - * Datatrak table * Conversion formula: - * * - * byte = 1 0 - 700 m * P = P0 * exp(-(g * M * h ) / (R * T0)) - * byte = 2 700 - 1700m * P0 = sealevel pressure = 101325 Pa - * byte = 3 1700 - 2700 m * g = grav. acceleration = 9,80665 m/s² - * byte = 4 2700 - * m * M = molar mass (dry air) = 0,0289644 Kg/mol - * * h = altitude over sea level (m) - * * R = Universal gas constant = 8,31447 J/(mol*K) - * * T0 = sea level standard temperature = 288,15 K - */ - read_bytes(1); - switch (tmp_1byte) { - case 1: - dt_dive->dc.surface_pressure.mbar = 1013; - break; - case 2: - dt_dive->dc.surface_pressure.mbar = 932; - break; - case 3: - dt_dive->dc.surface_pressure.mbar = 828; - break; - case 4: - dt_dive->dc.surface_pressure.mbar = 735; - break; - default: - dt_dive->dc.surface_pressure.mbar = 1013; - } - - /* - * Interval (minutes) - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF) - dt_dive->dc.surfacetime.seconds = (uint32_t) tmp_2bytes * 60; - - /* - * Weather, values table, 0 to 6 - * Subsurface don't have this record but we can use tags - */ - dt_dive->tag_list = NULL; - read_bytes(1); - switch (tmp_1byte) { - case 1: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "clear"))); - break; - case 2: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "misty"))); - break; - case 3: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fog"))); - break; - case 4: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "rain"))); - break; - case 5: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "storm"))); - break; - case 6: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "snow"))); - break; - default: - // unknown, do nothing - break; - } - - /* - * Air Temperature - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF) - dt_dive->dc.airtemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100)); - - /* - * Dive suit, values table, 0 to 6 - */ - read_bytes(1); - switch (tmp_1byte) { - case 1: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "No suit")); - break; - case 2: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Shorty")); - break; - case 3: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Combi")); - break; - case 4: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Wet suit")); - break; - case 5: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Semidry suit")); - break; - case 6: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Dry suit")); - break; - default: - // unknown, do nothing - break; - } - - /* - * Tank, volume size in liter*100. And initialize gasmix to air (default). - * Dtrak don't record init and end pressures, but consumed bar, so let's - * init a default pressure of 200 bar. - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF) { - dt_dive->cylinder[0].type.size.mliter = tmp_2bytes * 10; - dt_dive->cylinder[0].type.description = strdup(""); - dt_dive->cylinder[0].start.mbar = 200000; - dt_dive->cylinder[0].gasmix.he.permille = 0; - dt_dive->cylinder[0].gasmix.o2.permille = 210; - dt_dive->cylinder[0].manually_added = true; - } - - /* - * Maximum depth, in cm. - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF) - dt_dive->maxdepth.mm = dt_dive->dc.maxdepth.mm = (int32_t)tmp_2bytes * 10; - - /* - * Dive time in minutes. - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF) - dt_dive->duration.seconds = dt_dive->dc.duration.seconds = (uint32_t)tmp_2bytes * 60; - - /* - * Minimum water temperature in C*100. If unknown, set it to 0K which - * is subsurface's value for "unknown" - */ - read_bytes(2); - if (tmp_2bytes != 0x7fff) - dt_dive->watertemp.mkelvin = dt_dive->dc.watertemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100)); - else - dt_dive->watertemp.mkelvin = 0; - - /* - * Air used in bar*100. - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF && dt_dive->cylinder[0].type.size.mliter) - dt_dive->cylinder[0].gas_used.mliter = dt_dive->cylinder[0].type.size.mliter * (tmp_2bytes / 100.0); - - /* - * Dive Type 1 - Bit table. Subsurface don't have this record, but - * will use tags. Bits 0 and 1 are not used. Reuse coincident tags. - */ - read_bytes(1); - byte = byte_to_bits(tmp_1byte); - if (byte[2] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "no stop"))); - if (byte[3] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "deco"))); - if (byte[4] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "single ascent"))); - if (byte[5] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "multiple ascent"))); - if (byte[6] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fresh"))); - if (byte[7] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "salt water"))); - free(byte); - - /* - * Dive Type 2 - Bit table, use tags again - */ - read_bytes(1); - byte = byte_to_bits(tmp_1byte); - if (byte[0] != 0) { - taglist_add_tag(&dt_dive->tag_list, strdup("nitrox")); - is_nitrox = 1; - } - if (byte[1] != 0) { - taglist_add_tag(&dt_dive->tag_list, strdup("rebreather")); - is_SCR = 1; - dt_dive->dc.divemode = PSCR; - } - free(byte); - - /* - * Dive Activity 1 - Bit table, use tags again - */ - read_bytes(1); - byte = byte_to_bits(tmp_1byte); - if (byte[0] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "sight seeing"))); - if (byte[1] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "club dive"))); - if (byte[2] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instructor"))); - if (byte[3] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instruction"))); - if (byte[4] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "night"))); - if (byte[5] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "cave"))); - if (byte[6] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "ice"))); - if (byte[7] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "search"))); - free(byte); - - - /* - * Dive Activity 2 - Bit table, use tags again - */ - read_bytes(1); - byte = byte_to_bits(tmp_1byte); - if (byte[0] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "wreck"))); - if (byte[1] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "river"))); - if (byte[2] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "drift"))); - if (byte[3] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "photo"))); - if (byte[4] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "other"))); - free(byte); - - /* - * Other activities - String 1st byte = long - * Will put this in dive notes before the true notes - */ - read_bytes(1); - if (tmp_1byte != 0) { - read_string(tmp_string1); - snprintf(buffer, sizeof(buffer), "%s: %s\n", - QT_TRANSLATE_NOOP("gettextFromC", "Other activities"), - tmp_string1); - tmp_notes_str = strdup(buffer); - free(tmp_string1); - } - - /* - * Dive buddies - */ - read_bytes(1); - if (tmp_1byte != 0) { - read_string(tmp_string1); - dt_dive->buddy = strdup((char *)tmp_string1); - free(tmp_string1); - } - - /* - * Dive notes - */ - read_bytes(1); - if (tmp_1byte != 0) { - read_string(tmp_string1); - int len = snprintf(buffer, sizeof(buffer), "%s%s:\n%s", - tmp_notes_str ? tmp_notes_str : "", - QT_TRANSLATE_NOOP("gettextFromC", "Datatrak/Wlog notes"), - tmp_string1); - dt_dive->notes = calloc((len +1), 1); - dt_dive->notes = memcpy(dt_dive->notes, buffer, len); - free(tmp_string1); - if (tmp_notes_str != NULL) - free(tmp_notes_str); - } - - /* - * Alarms 1 - Bit table - Not in Subsurface, we use the profile - */ - read_bytes(1); - - /* - * Alarms 2 - Bit table - Not in Subsurface, we use the profile - */ - read_bytes(1); - - /* - * Dive number (in datatrak, after import user has to renumber) - */ - read_bytes(2); - dt_dive->number = tmp_2bytes; - - /* - * Computer timestamp - Useless for Subsurface - */ - read_bytes(4); - - /* - * Model - table - Not included 0x14, 0x24, 0x41, and 0x73 - * known to exist, but not its model name - To add in the future. - * Strangely 0x00 serves for manually added dives and a dc too, at - * least in EXAMPLE.LOG file, shipped with the software. - */ - read_bytes(1); - switch (tmp_1byte) { - case 0x00: - dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Manually entered dive")); - break; - case 0x1C: - dt_dive->dc.model = strdup("Aladin Air"); - break; - case 0x1D: - dt_dive->dc.model = strdup("Spiro Monitor 2 plus"); - break; - case 0x1E: - dt_dive->dc.model = strdup("Aladin Sport"); - break; - case 0x1F: - dt_dive->dc.model = strdup("Aladin Pro"); - break; - case 0x34: - dt_dive->dc.model = strdup("Aladin Air X"); - break; - case 0x3D: - dt_dive->dc.model = strdup("Spiro Monitor 2 plus"); - break; - case 0x3F: - dt_dive->dc.model = strdup("Mares Genius"); - break; - case 0x44: - dt_dive->dc.model = strdup("Aladin Air X"); - break; - case 0x48: - dt_dive->dc.model = strdup("Spiro Monitor 3 Air"); - break; - case 0xA4: - dt_dive->dc.model = strdup("Aladin Air X O2"); - break; - case 0xB1: - dt_dive->dc.model = strdup("Citizen Hyper Aqualand"); - break; - case 0xB2: - dt_dive->dc.model = strdup("Citizen ProMaster"); - break; - case 0xB3: - dt_dive->dc.model = strdup("Mares Guardian"); - break; - case 0xBC: - dt_dive->dc.model = strdup("Aladin Air X Nitrox"); - break; - case 0xF4: - dt_dive->dc.model = strdup("Aladin Air X Nitrox"); - break; - case 0xFF: - dt_dive->dc.model = strdup("Aladin Pro Nitrox"); - break; - default: - dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Unknown")); - break; - } - if ((tmp_1byte & 0xF0) == 0xF0) - is_nitrox = 1; - if ((tmp_1byte & 0xF0) == 0xA0) - is_O2 = 1; - - /* - * Air usage, unknown use. Probably allows or deny manually entering gas - * comsumption based on dc model - Useless for Subsurface - */ - read_bytes(1); - if (fseek(archivo, 6, 1) != 0) // jump over 6 bytes whitout known use - goto bail; - /* - * Profile data length - */ - read_bytes(2); - profile_length = tmp_2bytes; - if (profile_length != 0) { - /* - * 8 x 2 bytes for the tissues saturation useless for subsurface - * and other 6 bytes without known use - */ - if (fseek(archivo, 22, 1) != 0) - goto bail; - if (is_nitrox || is_O2) { - - /* - * CNS % (unsure) values table (only nitrox computers) - */ - read_bytes(1); - - /* - * % O2 in nitrox mix - (only nitrox and O2 computers but differents) - */ - read_bytes(1); - if (is_nitrox) { - dt_dive->cylinder[0].gasmix.o2.permille = - (tmp_1byte & 0x0F ? 20.0 + 2 * (tmp_1byte & 0x0F) : 21.0) * 10; - } else { - dt_dive->cylinder[0].gasmix.o2.permille = tmp_1byte * 10; - read_bytes(1) // Jump over one byte, unknown use - } - } - /* - * profileLength = Nº bytes, need to know how many samples are there. - * 2bytes per sample plus another one each three samples. Also includes the - * bytes jumped over (22) and the nitrox (2) or O2 (3). - */ - int samplenum = is_O2 ? (profile_length - 25) * 3 / 8 : (profile_length - 24) * 3 / 7; - - dc->events = calloc(samplenum, sizeof(struct event)); - dc->alloc_samples = samplenum; - dc->samples = 0; - dc->sample = calloc(samplenum, sizeof(struct sample)); - - dtrak_profile(dt_dive, archivo); - } - /* - * Initialize some dive data not supported by Datatrak/WLog - */ - if (!strcmp(dt_dive->dc.model, "Manually entered dive")) - dt_dive->dc.deviceid = 0; - else - dt_dive->dc.deviceid = 0xffffffff; - create_device_node(dt_dive->dc.model, dt_dive->dc.deviceid, "", "", dt_dive->dc.model); - dt_dive->dc.next = NULL; - if (!is_SCR && dt_dive->cylinder[0].type.size.mliter) { - dt_dive->cylinder[0].end.mbar = dt_dive->cylinder[0].start.mbar - - ((dt_dive->cylinder[0].gas_used.mliter / dt_dive->cylinder[0].type.size.mliter) * 1000); - } - return true; - -bail: - return false; -} - -void datatrak_import(const char *file, struct dive_table *table) -{ - FILE *archivo; - dtrakheader *fileheader = (dtrakheader *)malloc(sizeof(dtrakheader)); - int i = 0; - - if ((archivo = subsurface_fopen(file, "rb")) == NULL) { - report_error(translate("gettextFromC", "Error: couldn't open the file %s"), file); - free(fileheader); - return; - } - - /* - * Verify fileheader, get number of dives in datatrak divelog - */ - *fileheader = read_file_header(archivo); - while (i < fileheader->divesNum) { - struct dive *ptdive = alloc_dive(); - - if (!dt_dive_parser(archivo, ptdive)) { - report_error(translate("gettextFromC", "Error: no dive")); - free(ptdive); - } else { - record_dive(ptdive); - } - i++; - } - taglist_cleanup(&g_tag_list); - fclose(archivo); - sort_table(table); - free(fileheader); -} diff --git a/subsurface-core/datatrak.h b/subsurface-core/datatrak.h deleted file mode 100644 index 3a37e0465..000000000 --- a/subsurface-core/datatrak.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef DATATRAK_HEADER_H -#define DATATRAK_HEADER_H - -#include <string.h> - -typedef struct dtrakheader_ { - int header; //Must be 0xA100; - int divesNum; - int dc_serial_1; - int dc_serial_2; -} dtrakheader; - -#define read_bytes(_n) \ - switch (_n) { \ - case 1: \ - if (fread (&lector_bytes, sizeof(char), _n, archivo) != _n) \ - goto bail; \ - tmp_1byte = lector_bytes[0]; \ - break; \ - case 2: \ - if (fread (&lector_bytes, sizeof(char), _n, archivo) != _n) \ - goto bail; \ - tmp_2bytes = two_bytes_to_int (lector_bytes[1], lector_bytes[0]); \ - break; \ - default: \ - if (fread (&lector_word, sizeof(char), _n, archivo) != _n) \ - goto bail; \ - tmp_4bytes = four_bytes_to_long(lector_word[3], lector_word[2], lector_word[1], lector_word[0]); \ - break; \ - } - -#define read_string(_property) \ - unsigned char *_property##tmp = (unsigned char *)calloc(tmp_1byte + 1, 1); \ - if (fread((char *)_property##tmp, 1, tmp_1byte, archivo) != tmp_1byte) { \ - free(_property##tmp); \ - goto bail; \ - } \ - _property = (unsigned char *)strcat(to_utf8(_property##tmp), ""); \ - free(_property##tmp); - -#endif // DATATRAK_HEADER_H diff --git a/subsurface-core/deco.c b/subsurface-core/deco.c deleted file mode 100644 index 3cd8c4a16..000000000 --- a/subsurface-core/deco.c +++ /dev/null @@ -1,601 +0,0 @@ -/* calculate deco values - * based on Bühlmann ZHL-16b - * based on an implemention by heinrichs weikamp for the DR5 - * the original file was given to Subsurface under the GPLv2 - * by Matthias Heinrichs - * - * The implementation below is a fairly complete rewrite since then - * (C) Robert C. Helling 2013 and released under the GPLv2 - * - * add_segment() - add <seconds> at the given pressure, breathing gasmix - * deco_allowed_depth() - ceiling based on lead tissue, surface pressure, 3m increments or smooth - * set_gf() - set Buehlmann gradient factors - * clear_deco() - * cache_deco_state() - * restore_deco_state() - * dump_tissues() - */ -#include <math.h> -#include <string.h> -#include "dive.h" -#include <assert.h> -#include <planner.h> - -#define cube(x) (x * x * x) - -// Subsurface appears to produce marginally less conservative plans than our benchmarks -// Introduce 1.2% additional conservatism -#define subsurface_conservatism_factor 1.012 - - -extern bool in_planner(); - -extern pressure_t first_ceiling_pressure; - -//! Option structure for Buehlmann decompression. -struct buehlmann_config { - double satmult; //! safety at inert gas accumulation as percentage of effect (more than 100). - double desatmult; //! safety at inert gas depletion as percentage of effect (less than 100). - int last_deco_stop_in_mtr; //! depth of last_deco_stop. - double gf_high; //! gradient factor high (at surface). - double gf_low; //! gradient factor low (at bottom/start of deco calculation). - double gf_low_position_min; //! gf_low_position below surface_min_shallow. - bool gf_low_at_maxdepth; //! if true, gf_low applies at max depth instead of at deepest ceiling. -}; - -struct buehlmann_config buehlmann_config = { - .satmult = 1.0, - .desatmult = 1.01, - .last_deco_stop_in_mtr = 0, - .gf_high = 0.75, - .gf_low = 0.35, - .gf_low_position_min = 1.0, - .gf_low_at_maxdepth = false -}; - -//! Option structure for VPM-B decompression. -struct vpmb_config { - double crit_radius_N2; //! Critical radius of N2 nucleon (microns). - double crit_radius_He; //! Critical radius of He nucleon (microns). - double crit_volume_lambda; //! Constant corresponding to critical gas volume (bar * min). - double gradient_of_imperm; //! Gradient after which bubbles become impermeable (bar). - double surface_tension_gamma; //! Nucleons surface tension constant (N / bar = m2). - double skin_compression_gammaC; //! Skin compression gammaC (N / bar = m2). - double regeneration_time; //! Time needed for the bubble to regenerate to the start radius (min). - double other_gases_pressure; //! Always present pressure of other gasses in tissues (bar). -}; - -struct vpmb_config vpmb_config = { - .crit_radius_N2 = 0.55, - .crit_radius_He = 0.45, - .crit_volume_lambda = 199.58, - .gradient_of_imperm = 8.30865, // = 8.2 atm - .surface_tension_gamma = 0.18137175, // = 0.0179 N/msw - .skin_compression_gammaC = 2.6040525, // = 0.257 N/msw - .regeneration_time = 20160.0, - .other_gases_pressure = 0.1359888 -}; - -const double buehlmann_N2_a[] = { 1.1696, 1.0, 0.8618, 0.7562, - 0.62, 0.5043, 0.441, 0.4, - 0.375, 0.35, 0.3295, 0.3065, - 0.2835, 0.261, 0.248, 0.2327 }; - -const double buehlmann_N2_b[] = { 0.5578, 0.6514, 0.7222, 0.7825, - 0.8126, 0.8434, 0.8693, 0.8910, - 0.9092, 0.9222, 0.9319, 0.9403, - 0.9477, 0.9544, 0.9602, 0.9653 }; - -const double buehlmann_N2_t_halflife[] = { 5.0, 8.0, 12.5, 18.5, - 27.0, 38.3, 54.3, 77.0, - 109.0, 146.0, 187.0, 239.0, - 305.0, 390.0, 498.0, 635.0 }; - -const double buehlmann_N2_factor_expositon_one_second[] = { - 2.30782347297664E-003, 1.44301447809736E-003, 9.23769302935806E-004, 6.24261986779007E-004, - 4.27777107246730E-004, 3.01585140931371E-004, 2.12729727268379E-004, 1.50020603047807E-004, - 1.05980191127841E-004, 7.91232600646508E-005, 6.17759153688224E-005, 4.83354552742732E-005, - 3.78761777920511E-005, 2.96212356654113E-005, 2.31974277413727E-005, 1.81926738960225E-005 -}; - -const double buehlmann_He_a[] = { 1.6189, 1.383, 1.1919, 1.0458, - 0.922, 0.8205, 0.7305, 0.6502, - 0.595, 0.5545, 0.5333, 0.5189, - 0.5181, 0.5176, 0.5172, 0.5119 }; - -const double buehlmann_He_b[] = { 0.4770, 0.5747, 0.6527, 0.7223, - 0.7582, 0.7957, 0.8279, 0.8553, - 0.8757, 0.8903, 0.8997, 0.9073, - 0.9122, 0.9171, 0.9217, 0.9267 }; - -const double buehlmann_He_t_halflife[] = { 1.88, 3.02, 4.72, 6.99, - 10.21, 14.48, 20.53, 29.11, - 41.20, 55.19, 70.69, 90.34, - 115.29, 147.42, 188.24, 240.03 }; - -const double buehlmann_He_factor_expositon_one_second[] = { - 6.12608039419837E-003, 3.81800836683133E-003, 2.44456078654209E-003, 1.65134647076792E-003, - 1.13084424730725E-003, 7.97503165599123E-004, 5.62552521860549E-004, 3.96776399429366E-004, - 2.80360036664540E-004, 2.09299583354805E-004, 1.63410794820518E-004, 1.27869320250551E-004, - 1.00198406028040E-004, 7.83611475491108E-005, 6.13689891868496E-005, 4.81280465299827E-005 -}; - -const double conservatism_lvls[] = { 1.0, 1.05, 1.12, 1.22, 1.35 }; - -/* Inspired gas loading equations depend on the partial pressure of inert gas in the alveolar. - * P_alv = (P_amb - P_H2O + (1 - Rq) / Rq * P_CO2) * f - * where: - * P_alv alveolar partial pressure of inert gas - * P_amb ambient pressure - * P_H2O water vapour partial pressure = ~0.0627 bar - * P_CO2 carbon dioxide partial pressure = ~0.0534 bar - * Rq respiratory quotient (O2 consumption / CO2 production) - * f fraction of inert gas - * - * In our calculations, we simplify this to use an effective water vapour pressure - * WV = P_H20 - (1 - Rq) / Rq * P_CO2 - * - * Buhlmann ignored the contribution of CO2 (i.e. Rq = 1.0), whereas Schreiner adopted Rq = 0.8. - * WV_Buhlmann = PP_H2O = 0.0627 bar - * WV_Schreiner = 0.0627 - (1 - 0.8) / Rq * 0.0534 = 0.0493 bar - - * Buhlmann calculations use the Buhlmann value, VPM-B calculations use the Schreiner value. -*/ -#define WV_PRESSURE 0.0627 // water vapor pressure in bar, based on respiratory quotient Rq = 1.0 (Buhlmann value) -#define WV_PRESSURE_SCHREINER 0.0493 // water vapor pressure in bar, based on respiratory quotient Rq = 0.8 (Schreiner value) - -#define DECO_STOPS_MULTIPLIER_MM 3000.0 -#define NITROGEN_FRACTION 0.79 - -double tissue_n2_sat[16]; -double tissue_he_sat[16]; -int ci_pointing_to_guiding_tissue; -double gf_low_pressure_this_dive; -#define TISSUE_ARRAY_SZ sizeof(tissue_n2_sat) - -double tolerated_by_tissue[16]; -double tissue_inertgas_saturation[16]; -double buehlmann_inertgas_a[16], buehlmann_inertgas_b[16]; - -double max_n2_crushing_pressure[16]; -double max_he_crushing_pressure[16]; - -double crushing_onset_tension[16]; // total inert gas tension in the t* moment -double n2_regen_radius[16]; // rs -double he_regen_radius[16]; -double max_ambient_pressure; // last moment we were descending - -double bottom_n2_gradient[16]; -double bottom_he_gradient[16]; - -double initial_n2_gradient[16]; -double initial_he_gradient[16]; - -double get_crit_radius_He() -{ - if (prefs.conservatism_level <= 4) - return vpmb_config.crit_radius_He * conservatism_lvls[prefs.conservatism_level] * subsurface_conservatism_factor; - return vpmb_config.crit_radius_He; -} - -double get_crit_radius_N2() -{ - if (prefs.conservatism_level <= 4) - return vpmb_config.crit_radius_N2 * conservatism_lvls[prefs.conservatism_level] * subsurface_conservatism_factor; - return vpmb_config.crit_radius_N2; -} - -// Solve another cubic equation, this time -// x^3 - B x - C == 0 -// Use trigonometric formula for negative discriminants (see Wikipedia for details) - -double solve_cubic2(double B, double C) -{ - double discriminant = 27 * C * C - 4 * cube(B); - if (discriminant < 0.0) { - return 2.0 * sqrt(B / 3.0) * cos(acos(3.0 * C * sqrt(3.0 / B) / (2.0 * B)) / 3.0); - } - - double denominator = pow(9 * C + sqrt(3 * discriminant), 1 / 3.0); - - return pow(2.0 / 3.0, 1.0 / 3.0) * B / denominator + denominator / pow(18.0, 1.0 / 3.0); -} - -// This is a simplified formula avoiding radii. It uses the fact that Boyle's law says -// pV = (G + P_amb) / G^3 is constant to solve for the new gradient G. - -double update_gradient(double next_stop_pressure, double first_gradient) -{ - double B = cube(first_gradient) / (first_ceiling_pressure.mbar / 1000.0 + first_gradient); - double C = next_stop_pressure * B; - - double new_gradient = solve_cubic2(B, C); - - if (new_gradient < 0.0) - report_error("Negative gradient encountered!"); - return new_gradient; -} - -double vpmb_tolerated_ambient_pressure(double reference_pressure, int ci) -{ - double n2_gradient, he_gradient, total_gradient; - - if (reference_pressure >= first_ceiling_pressure.mbar / 1000.0 || !first_ceiling_pressure.mbar) { - n2_gradient = bottom_n2_gradient[ci]; - he_gradient = bottom_he_gradient[ci]; - } else { - n2_gradient = update_gradient(reference_pressure, bottom_n2_gradient[ci]); - he_gradient = update_gradient(reference_pressure, bottom_he_gradient[ci]); - } - - total_gradient = ((n2_gradient * tissue_n2_sat[ci]) + (he_gradient * tissue_he_sat[ci])) / (tissue_n2_sat[ci] + tissue_he_sat[ci]); - - return tissue_n2_sat[ci] + tissue_he_sat[ci] + vpmb_config.other_gases_pressure - total_gradient; -} - - -double tissue_tolerance_calc(const struct dive *dive, double pressure) -{ - int ci = -1; - double ret_tolerance_limit_ambient_pressure = 0.0; - double gf_high = buehlmann_config.gf_high; - double gf_low = buehlmann_config.gf_low; - double surface = get_surface_pressure_in_mbar(dive, true) / 1000.0; - double lowest_ceiling = 0.0; - double tissue_lowest_ceiling[16]; - - if (prefs.deco_mode != VPMB) { - for (ci = 0; ci < 16; ci++) { - tissue_inertgas_saturation[ci] = tissue_n2_sat[ci] + tissue_he_sat[ci]; - buehlmann_inertgas_a[ci] = ((buehlmann_N2_a[ci] * tissue_n2_sat[ci]) + (buehlmann_He_a[ci] * tissue_he_sat[ci])) / tissue_inertgas_saturation[ci]; - buehlmann_inertgas_b[ci] = ((buehlmann_N2_b[ci] * tissue_n2_sat[ci]) + (buehlmann_He_b[ci] * tissue_he_sat[ci])) / tissue_inertgas_saturation[ci]; - - - /* tolerated = (tissue_inertgas_saturation - buehlmann_inertgas_a) * buehlmann_inertgas_b; */ - - tissue_lowest_ceiling[ci] = (buehlmann_inertgas_b[ci] * tissue_inertgas_saturation[ci] - gf_low * buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci]) / - ((1.0 - buehlmann_inertgas_b[ci]) * gf_low + buehlmann_inertgas_b[ci]); - if (tissue_lowest_ceiling[ci] > lowest_ceiling) - lowest_ceiling = tissue_lowest_ceiling[ci]; - if (!buehlmann_config.gf_low_at_maxdepth) { - if (lowest_ceiling > gf_low_pressure_this_dive) - gf_low_pressure_this_dive = lowest_ceiling; - } - } - for (ci = 0; ci < 16; ci++) { - double tolerated; - - if ((surface / buehlmann_inertgas_b[ci] + buehlmann_inertgas_a[ci] - surface) * gf_high + surface < - (gf_low_pressure_this_dive / buehlmann_inertgas_b[ci] + buehlmann_inertgas_a[ci] - gf_low_pressure_this_dive) * gf_low + gf_low_pressure_this_dive) - tolerated = (-buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci] * (gf_high * gf_low_pressure_this_dive - gf_low * surface) - - (1.0 - buehlmann_inertgas_b[ci]) * (gf_high - gf_low) * gf_low_pressure_this_dive * surface + - buehlmann_inertgas_b[ci] * (gf_low_pressure_this_dive - surface) * tissue_inertgas_saturation[ci]) / - (-buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci] * (gf_high - gf_low) + - (1.0 - buehlmann_inertgas_b[ci]) * (gf_low * gf_low_pressure_this_dive - gf_high * surface) + - buehlmann_inertgas_b[ci] * (gf_low_pressure_this_dive - surface)); - else - tolerated = ret_tolerance_limit_ambient_pressure; - - - tolerated_by_tissue[ci] = tolerated; - - if (tolerated >= ret_tolerance_limit_ambient_pressure) { - ci_pointing_to_guiding_tissue = ci; - ret_tolerance_limit_ambient_pressure = tolerated; - } - } - } else { - // VPM-B ceiling - double reference_pressure; - - ret_tolerance_limit_ambient_pressure = pressure; - // The Boyle compensated gradient depends on ambient pressure. For the ceiling, this should set the ambient pressure. - do { - reference_pressure = ret_tolerance_limit_ambient_pressure; - ret_tolerance_limit_ambient_pressure = 0.0; - for (ci = 0; ci < 16; ci++) { - double tolerated = vpmb_tolerated_ambient_pressure(reference_pressure, ci); - if (tolerated >= ret_tolerance_limit_ambient_pressure) { - ci_pointing_to_guiding_tissue = ci; - ret_tolerance_limit_ambient_pressure = tolerated; - } - tolerated_by_tissue[ci] = tolerated; - } - // We are doing ok if the gradient was computed within ten centimeters of the ceiling. - } while (fabs(ret_tolerance_limit_ambient_pressure - reference_pressure) > 0.01); - } - return ret_tolerance_limit_ambient_pressure; -} - -/* - * Return buelman factor for a particular period and tissue index. - * - * We cache the last factor, since we commonly call this with the - * same values... We have a special "fixed cache" for the one second - * case, although I wonder if that's even worth it considering the - * more general-purpose cache. - */ -struct factor_cache { - int last_period; - double last_factor; -}; - -double n2_factor(int period_in_seconds, int ci) -{ - static struct factor_cache cache[16]; - - if (period_in_seconds == 1) - return buehlmann_N2_factor_expositon_one_second[ci]; - - if (period_in_seconds != cache[ci].last_period) { - cache[ci].last_period = period_in_seconds; - cache[ci].last_factor = 1 - pow(2.0, -period_in_seconds / (buehlmann_N2_t_halflife[ci] * 60)); - } - - return cache[ci].last_factor; -} - -double he_factor(int period_in_seconds, int ci) -{ - static struct factor_cache cache[16]; - - if (period_in_seconds == 1) - return buehlmann_He_factor_expositon_one_second[ci]; - - if (period_in_seconds != cache[ci].last_period) { - cache[ci].last_period = period_in_seconds; - cache[ci].last_factor = 1 - pow(2.0, -period_in_seconds / (buehlmann_He_t_halflife[ci] * 60)); - } - - return cache[ci].last_factor; -} - -double calc_surface_phase(double surface_pressure, double he_pressure, double n2_pressure, double he_time_constant, double n2_time_constant) -{ - double inspired_n2 = (surface_pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE)) * NITROGEN_FRACTION; - - if (n2_pressure > inspired_n2) - return (he_pressure / he_time_constant + (n2_pressure - inspired_n2) / n2_time_constant) / (he_pressure + n2_pressure - inspired_n2); - - if (he_pressure + n2_pressure >= inspired_n2){ - double gradient_decay_time = 1.0 / (n2_time_constant - he_time_constant) * log ((inspired_n2 - n2_pressure) / he_pressure); - double gradients_integral = he_pressure / he_time_constant * (1.0 - exp(-he_time_constant * gradient_decay_time)) + (n2_pressure - inspired_n2) / n2_time_constant * (1.0 - exp(-n2_time_constant * gradient_decay_time)); - return gradients_integral / (he_pressure + n2_pressure - inspired_n2); - } - - return 0; -} - -void vpmb_start_gradient() -{ - int ci; - - for (ci = 0; ci < 16; ++ci) { - initial_n2_gradient[ci] = bottom_n2_gradient[ci] = 2.0 * (vpmb_config.surface_tension_gamma / vpmb_config.skin_compression_gammaC) * ((vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma) / n2_regen_radius[ci]); - initial_he_gradient[ci] = bottom_he_gradient[ci] = 2.0 * (vpmb_config.surface_tension_gamma / vpmb_config.skin_compression_gammaC) * ((vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma) / he_regen_radius[ci]); - } -} - -void vpmb_next_gradient(double deco_time, double surface_pressure) -{ - int ci; - double n2_b, n2_c; - double he_b, he_c; - double desat_time; - deco_time /= 60.0; - - for (ci = 0; ci < 16; ++ci) { - desat_time = deco_time + calc_surface_phase(surface_pressure, tissue_he_sat[ci], tissue_n2_sat[ci], log(2.0) / buehlmann_He_t_halflife[ci], log(2.0) / buehlmann_N2_t_halflife[ci]); - - n2_b = initial_n2_gradient[ci] + (vpmb_config.crit_volume_lambda * vpmb_config.surface_tension_gamma) / (vpmb_config.skin_compression_gammaC * desat_time); - he_b = initial_he_gradient[ci] + (vpmb_config.crit_volume_lambda * vpmb_config.surface_tension_gamma) / (vpmb_config.skin_compression_gammaC * desat_time); - - n2_c = vpmb_config.surface_tension_gamma * vpmb_config.surface_tension_gamma * vpmb_config.crit_volume_lambda * max_n2_crushing_pressure[ci]; - n2_c = n2_c / (vpmb_config.skin_compression_gammaC * vpmb_config.skin_compression_gammaC * desat_time); - he_c = vpmb_config.surface_tension_gamma * vpmb_config.surface_tension_gamma * vpmb_config.crit_volume_lambda * max_he_crushing_pressure[ci]; - he_c = he_c / (vpmb_config.skin_compression_gammaC * vpmb_config.skin_compression_gammaC * desat_time); - - bottom_n2_gradient[ci] = 0.5 * ( n2_b + sqrt(n2_b * n2_b - 4.0 * n2_c)); - bottom_he_gradient[ci] = 0.5 * ( he_b + sqrt(he_b * he_b - 4.0 * he_c)); - } -} - -// A*r^3 - B*r^2 - C == 0 -// Solved with the help of mathematica - -double solve_cubic(double A, double B, double C) -{ - double BA = B/A; - double CA = C/A; - - double discriminant = CA * (4 * cube(BA) + 27 * CA); - - // Let's make sure we have a real solution: - if (discriminant < 0.0) { - // This should better not happen - report_error("Complex solution for inner pressure encountered!\n A=%f\tB=%f\tC=%f\n", A, B, C); - return 0.0; - } - double denominator = pow(cube(BA) + 1.5 * (9 * CA + sqrt(3.0) * sqrt(discriminant)), 1/3.0); - return (BA + BA * BA / denominator + denominator) / 3.0; - -} - - -void nuclear_regeneration(double time) -{ - time /= 60.0; - int ci; - double crushing_radius_N2, crushing_radius_He; - for (ci = 0; ci < 16; ++ci) { - //rm - crushing_radius_N2 = 1.0 / (max_n2_crushing_pressure[ci] / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / get_crit_radius_N2()); - crushing_radius_He = 1.0 / (max_he_crushing_pressure[ci] / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / get_crit_radius_He()); - //rs - n2_regen_radius[ci] = crushing_radius_N2 + (get_crit_radius_N2() - crushing_radius_N2) * (1.0 - exp (-time / vpmb_config.regeneration_time)); - he_regen_radius[ci] = crushing_radius_He + (get_crit_radius_He() - crushing_radius_He) * (1.0 - exp (-time / vpmb_config.regeneration_time)); - } -} - - -// Calculates the nucleons inner pressure during the impermeable period -double calc_inner_pressure(double crit_radius, double onset_tension, double current_ambient_pressure) -{ - double onset_radius = 1.0 / (vpmb_config.gradient_of_imperm / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / crit_radius); - - - double A = current_ambient_pressure - vpmb_config.gradient_of_imperm + (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) / onset_radius; - double B = 2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma); - double C = onset_tension * pow(onset_radius, 3); - - double current_radius = solve_cubic(A, B, C); - - return onset_tension * onset_radius * onset_radius * onset_radius / (current_radius * current_radius * current_radius); -} - -// Calculates the crushing pressure in the given moment. Updates crushing_onset_tension and critical radius if needed -void calc_crushing_pressure(double pressure) -{ - int ci; - double gradient; - double gas_tension; - double n2_crushing_pressure, he_crushing_pressure; - double n2_inner_pressure, he_inner_pressure; - - for (ci = 0; ci < 16; ++ci) { - gas_tension = tissue_n2_sat[ci] + tissue_he_sat[ci] + vpmb_config.other_gases_pressure; - gradient = pressure - gas_tension; - - if (gradient <= vpmb_config.gradient_of_imperm) { // permeable situation - n2_crushing_pressure = he_crushing_pressure = gradient; - crushing_onset_tension[ci] = gas_tension; - } - else { // impermeable - if (max_ambient_pressure >= pressure) - return; - - n2_inner_pressure = calc_inner_pressure(get_crit_radius_N2(), crushing_onset_tension[ci], pressure); - he_inner_pressure = calc_inner_pressure(get_crit_radius_He(), crushing_onset_tension[ci], pressure); - - n2_crushing_pressure = pressure - n2_inner_pressure; - he_crushing_pressure = pressure - he_inner_pressure; - } - max_n2_crushing_pressure[ci] = MAX(max_n2_crushing_pressure[ci], n2_crushing_pressure); - max_he_crushing_pressure[ci] = MAX(max_he_crushing_pressure[ci], he_crushing_pressure); - } - max_ambient_pressure = MAX(pressure, max_ambient_pressure); -} - -/* add period_in_seconds at the given pressure and gas to the deco calculation */ -void add_segment(double pressure, const struct gasmix *gasmix, int period_in_seconds, int ccpo2, const struct dive *dive, int sac) -{ - (void) sac; - int ci; - struct gas_pressures pressures; - - fill_pressures(&pressures, pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE), - gasmix, (double) ccpo2 / 1000.0, dive->dc.divemode); - - if (buehlmann_config.gf_low_at_maxdepth && pressure > gf_low_pressure_this_dive) - gf_low_pressure_this_dive = pressure; - - for (ci = 0; ci < 16; ci++) { - double pn2_oversat = pressures.n2 - tissue_n2_sat[ci]; - double phe_oversat = pressures.he - tissue_he_sat[ci]; - double n2_f = n2_factor(period_in_seconds, ci); - double he_f = he_factor(period_in_seconds, ci); - double n2_satmult = pn2_oversat > 0 ? buehlmann_config.satmult : buehlmann_config.desatmult; - double he_satmult = phe_oversat > 0 ? buehlmann_config.satmult : buehlmann_config.desatmult; - - tissue_n2_sat[ci] += n2_satmult * pn2_oversat * n2_f; - tissue_he_sat[ci] += he_satmult * phe_oversat * he_f; - } - if(prefs.deco_mode == VPMB) - calc_crushing_pressure(pressure); - return; -} - -void dump_tissues() -{ - int ci; - printf("N2 tissues:"); - for (ci = 0; ci < 16; ci++) - printf(" %6.3e", tissue_n2_sat[ci]); - printf("\nHe tissues:"); - for (ci = 0; ci < 16; ci++) - printf(" %6.3e", tissue_he_sat[ci]); - printf("\n"); -} - -void clear_deco(double surface_pressure) -{ - int ci; - for (ci = 0; ci < 16; ci++) { - tissue_n2_sat[ci] = (surface_pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE)) * N2_IN_AIR / 1000; - tissue_he_sat[ci] = 0.0; - max_n2_crushing_pressure[ci] = 0.0; - max_he_crushing_pressure[ci] = 0.0; - n2_regen_radius[ci] = get_crit_radius_N2(); - he_regen_radius[ci] = get_crit_radius_He(); - } - gf_low_pressure_this_dive = surface_pressure; - if (!buehlmann_config.gf_low_at_maxdepth) - gf_low_pressure_this_dive += buehlmann_config.gf_low_position_min; - max_ambient_pressure = 0.0; -} - -void cache_deco_state(char **cached_datap) -{ - char *data = *cached_datap; - - if (!data) { - data = malloc(2 * TISSUE_ARRAY_SZ + sizeof(double) + sizeof(int)); - *cached_datap = data; - } - memcpy(data, tissue_n2_sat, TISSUE_ARRAY_SZ); - data += TISSUE_ARRAY_SZ; - memcpy(data, tissue_he_sat, TISSUE_ARRAY_SZ); - data += TISSUE_ARRAY_SZ; - memcpy(data, &gf_low_pressure_this_dive, sizeof(double)); - data += sizeof(double); - memcpy(data, &ci_pointing_to_guiding_tissue, sizeof(int)); -} - -void restore_deco_state(char *data) -{ - memcpy(tissue_n2_sat, data, TISSUE_ARRAY_SZ); - data += TISSUE_ARRAY_SZ; - memcpy(tissue_he_sat, data, TISSUE_ARRAY_SZ); - data += TISSUE_ARRAY_SZ; - memcpy(&gf_low_pressure_this_dive, data, sizeof(double)); - data += sizeof(double); - memcpy(&ci_pointing_to_guiding_tissue, data, sizeof(int)); -} - -int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth) -{ - int depth; - double pressure_delta; - - /* Avoid negative depths */ - pressure_delta = tissues_tolerance > surface_pressure ? tissues_tolerance - surface_pressure : 0.0; - - depth = rel_mbar_to_depth(pressure_delta * 1000, dive); - - if (!smooth) - depth = ceil(depth / DECO_STOPS_MULTIPLIER_MM) * DECO_STOPS_MULTIPLIER_MM; - - if (depth > 0 && depth < buehlmann_config.last_deco_stop_in_mtr * 1000) - depth = buehlmann_config.last_deco_stop_in_mtr * 1000; - - return depth; -} - -void set_gf(short gflow, short gfhigh, bool gf_low_at_maxdepth) -{ - if (gflow != -1) - buehlmann_config.gf_low = (double)gflow / 100.0; - if (gfhigh != -1) - buehlmann_config.gf_high = (double)gfhigh / 100.0; - buehlmann_config.gf_low_at_maxdepth = gf_low_at_maxdepth; -} diff --git a/subsurface-core/deco.h b/subsurface-core/deco.h deleted file mode 100644 index fd3b94a9f..000000000 --- a/subsurface-core/deco.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef DECO_H -#define DECO_H - -#ifdef __cplusplus -extern "C" { -#endif - -extern double tolerated_by_tissue[]; -extern double buehlmann_N2_t_halflife[]; -extern double tissue_inertgas_saturation[16]; -extern double buehlmann_inertgas_a[16], buehlmann_inertgas_b[16]; -extern double gf_low_pressure_this_dive; - -extern int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth); - -#ifdef __cplusplus -} -#endif - -#endif // DECO_H diff --git a/subsurface-core/device.c b/subsurface-core/device.c deleted file mode 100644 index 6c4452f78..000000000 --- a/subsurface-core/device.c +++ /dev/null @@ -1,184 +0,0 @@ -#include <string.h> -#include "dive.h" -#include "device.h" - -/* - * Good fake dive profiles are hard. - * - * "depthtime" is the integral of the dive depth over - * time ("area" of the dive profile). We want that - * area to match the average depth (avg_d*max_t). - * - * To do that, we generate a 6-point profile: - * - * (0, 0) - * (t1, max_d) - * (t2, max_d) - * (t3, d) - * (t4, d) - * (max_t, 0) - * - * with the same ascent/descent rates between the - * different depths. - * - * NOTE: avg_d, max_d and max_t are given constants. - * The rest we can/should play around with to get a - * good-looking profile. - * - * That six-point profile gives a total area of: - * - * (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) - * - * And the "same ascent/descent rates" requirement - * gives us (time per depth must be same): - * - * t1 / max_d = (t3-t2) / (max_d-d) - * t1 / max_d = (max_t-t4) / d - * - * We also obviously require: - * - * 0 <= t1 <= t2 <= t3 <= t4 <= max_t - * - * Let us call 'd_frac = d / max_d', and we get: - * - * Total area must match average depth-time: - * - * (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) = avg_d*max_t - * max_d*(max_t-t1-(1-d_frac)*(t4-t3)) = avg_d*max_t - * max_t-t1-(1-d_frac)*(t4-t3) = avg_d*max_t/max_d - * t1+(1-d_frac)*(t4-t3) = max_t*(1-avg_d/max_d) - * - * and descent slope must match ascent slopes: - * - * t1 / max_d = (t3-t2) / (max_d*(1-d_frac)) - * t1 = (t3-t2)/(1-d_frac) - * - * and - * - * t1 / max_d = (max_t-t4) / (max_d*d_frac) - * t1 = (max_t-t4)/d_frac - * - * In general, we have more free variables than we have constraints, - * but we can aim for certain basics, like a good ascent slope. - */ -static int fill_samples(struct sample *s, int max_d, int avg_d, int max_t, double slope, double d_frac) -{ - double t_frac = max_t * (1 - avg_d / (double)max_d); - int t1 = max_d / slope; - int t4 = max_t - t1 * d_frac; - int t3 = t4 - (t_frac - t1) / (1 - d_frac); - int t2 = t3 - t1 * (1 - d_frac); - - if (t1 < 0 || t1 > t2 || t2 > t3 || t3 > t4 || t4 > max_t) - return 0; - - s[1].time.seconds = t1; - s[1].depth.mm = max_d; - s[2].time.seconds = t2; - s[2].depth.mm = max_d; - s[3].time.seconds = t3; - s[3].depth.mm = max_d * d_frac; - s[4].time.seconds = t4; - s[4].depth.mm = max_d * d_frac; - - return 1; -} - -/* we have no average depth; instead of making up a random average depth - * we should assume either a PADI rectangular profile (for short and/or - * shallow dives) or more reasonably a six point profile with a 3 minute - * safety stop at 5m */ -static void fill_samples_no_avg(struct sample *s, int max_d, int max_t, double slope) -{ - // shallow or short dives are just trapecoids based on the given slope - if (max_d < 10000 || max_t < 600) { - s[1].time.seconds = max_d / slope; - s[1].depth.mm = max_d; - s[2].time.seconds = max_t - max_d / slope; - s[2].depth.mm = max_d; - } else { - s[1].time.seconds = max_d / slope; - s[1].depth.mm = max_d; - s[2].time.seconds = max_t - max_d / slope - 180; - s[2].depth.mm = max_d; - s[3].time.seconds = max_t - 5000 / slope - 180; - s[3].depth.mm = 5000; - s[4].time.seconds = max_t - 5000 / slope; - s[4].depth.mm = 5000; - } -} - -struct divecomputer *fake_dc(struct divecomputer *dc, bool alloc) -{ - static struct sample fake_samples[6]; - static struct divecomputer fakedc; - struct sample *fake = fake_samples; - - fakedc = (*dc); - if (alloc) - fake = malloc(sizeof(fake_samples)); - - fakedc.sample = fake; - fakedc.samples = 6; - - /* The dive has no samples, so create a few fake ones */ - int max_t = dc->duration.seconds; - int max_d = dc->maxdepth.mm; - int avg_d = dc->meandepth.mm; - - memset(fake, 0, sizeof(fake_samples)); - fake[5].time.seconds = max_t; - if (!max_t || !max_d) - return &fakedc; - - /* - * We want to fake the profile so that the average - * depth ends up correct. However, in the absence of - * a reasonable average, let's just make something - * up. Note that 'avg_d == max_d' is _not_ a reasonable - * average. - * We explicitly treat avg_d == 0 differently */ - if (avg_d == 0) { - /* we try for a sane slope, but bow to the insanity of - * the user supplied data */ - fill_samples_no_avg(fake, max_d, max_t, MAX(2.0 * max_d / max_t, 5000.0 / 60)); - if (fake[3].time.seconds == 0) { // just a 4 point profile - fakedc.samples = 4; - fake[3].time.seconds = max_t; - } - return &fakedc; - } - if (avg_d < max_d / 10 || avg_d >= max_d) { - avg_d = (max_d + 10000) / 3; - if (avg_d > max_d) - avg_d = max_d * 2 / 3; - } - if (!avg_d) - avg_d = 1; - - /* - * Ok, first we try a basic profile with a specific ascent - * rate (5 meters per minute) and d_frac (1/3). - */ - if (fill_samples(fake, max_d, avg_d, max_t, 5000.0 / 60, 0.33)) - return &fakedc; - - /* - * Ok, assume that didn't work because we cannot make the - * average come out right because it was a quick deep dive - * followed by a much shallower region - */ - if (fill_samples(fake, max_d, avg_d, max_t, 10000.0 / 60, 0.10)) - return &fakedc; - - /* - * Uhhuh. That didn't work. We'd need to find a good combination that - * satisfies our constraints. Currently, we don't, we just give insane - * slopes. - */ - if (fill_samples(fake, max_d, avg_d, max_t, 10000.0, 0.01)) - return &fakedc; - - /* Even that didn't work? Give up, there's something wrong */ - return &fakedc; -} diff --git a/subsurface-core/device.h b/subsurface-core/device.h deleted file mode 100644 index 8a00b96d3..000000000 --- a/subsurface-core/device.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef DEVICE_H -#define DEVICE_H - -#ifdef __cplusplus -#include "dive.h" -extern "C" { -#endif - -extern struct divecomputer *fake_dc(struct divecomputer *dc, bool alloc); -extern void create_device_node(const char *model, uint32_t deviceid, const char *serial, const char *firmware, const char *nickname); -extern void call_for_each_dc(void *f, void (*callback)(void *, const char *, uint32_t, - const char *, const char *, const char *), bool select_only); - -#ifdef __cplusplus -} -#endif - -#endif // DEVICE_H diff --git a/subsurface-core/devicedetails.cpp b/subsurface-core/devicedetails.cpp deleted file mode 100644 index a917a4d0e..000000000 --- a/subsurface-core/devicedetails.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "devicedetails.h" - -gas::gas(unsigned char oxygen, unsigned char helium, unsigned char type, unsigned char depth) : - oxygen(oxygen), helium(helium), type(type), depth(depth) -{ -} - -setpoint::setpoint(unsigned char sp, unsigned char depth) : - sp(sp), depth(depth) -{ -} - -DeviceDetails::DeviceDetails(QObject *parent) : - QObject(parent), - data(0), - syncTime(false), - setPointFallback(0), - ccrMode(0), - calibrationGas(0), - diveMode(0), - decoType(0), - ppO2Max(0), - ppO2Min(0), - futureTTS(0), - gfLow(0), - gfHigh(0), - aGFLow(0), - aGFHigh(0), - aGFSelectable(0), - saturation(0), - desaturation(0), - lastDeco(0), - brightness(0), - units(0), - samplingRate(0), - salinity(0), - diveModeColor(0), - language(0), - dateFormat(0), - compassGain(0), - pressureSensorOffset(0), - flipScreen(0), - safetyStop(0), - maxDepth(0), - totalTime(0), - numberOfDives(0), - altitude(0), - personalSafety(0), - timeFormat(0), - lightEnabled(false), - light(0), - alarmTimeEnabled(false), - alarmTime(0), - alarmDepthEnabled(false), - alarmDepth(0), - leftButtonSensitivity(0), - rightButtonSensitivity(0), - bottomGasConsumption(0), - decoGasConsumption(0), - modWarning(false), - dynamicAscendRate(false), - graphicalSpeedIndicator(false), - alwaysShowppO2(false), - tempSensorOffset(0), - safetyStopLength(0), - safetyStopStartDepth(0), - safetyStopEndDepth(0), - safetyStopResetDepth(0) -{ -} diff --git a/subsurface-core/devicedetails.h b/subsurface-core/devicedetails.h deleted file mode 100644 index ff3009bc5..000000000 --- a/subsurface-core/devicedetails.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef DEVICEDETAILS_H -#define DEVICEDETAILS_H - -#include <QObject> -#include <QDateTime> -#include "libdivecomputer.h" - -struct gas { - unsigned char oxygen; - unsigned char helium; - unsigned char type; - unsigned char depth; - gas(unsigned char oxygen = 0, unsigned char helium = 0, unsigned char type = 0, unsigned char depth = 0); -}; - -struct setpoint { - unsigned char sp; - unsigned char depth; - setpoint(unsigned char sp = 0, unsigned char depth = 0); -}; - -class DeviceDetails : public QObject -{ - Q_OBJECT -public: - explicit DeviceDetails(QObject *parent = 0); - - device_data_t *data; - QString serialNo; - QString firmwareVersion; - QString customText; - QString model; - bool syncTime; - gas gas1; - gas gas2; - gas gas3; - gas gas4; - gas gas5; - gas dil1; - gas dil2; - gas dil3; - gas dil4; - gas dil5; - setpoint sp1; - setpoint sp2; - setpoint sp3; - setpoint sp4; - setpoint sp5; - bool setPointFallback; - int ccrMode; - int calibrationGas; - int diveMode; - int decoType; - int ppO2Max; - int ppO2Min; - int futureTTS; - int gfLow; - int gfHigh; - int aGFLow; - int aGFHigh; - int aGFSelectable; - int saturation; - int desaturation; - int lastDeco; - int brightness; - int units; - int samplingRate; - int salinity; - int diveModeColor; - int language; - int dateFormat; - int compassGain; - int pressureSensorOffset; - bool flipScreen; - bool safetyStop; - int maxDepth; - int totalTime; - int numberOfDives; - int altitude; - int personalSafety; - int timeFormat; - bool lightEnabled; - int light; - bool alarmTimeEnabled; - int alarmTime; - bool alarmDepthEnabled; - int alarmDepth; - int leftButtonSensitivity; - int rightButtonSensitivity; - int bottomGasConsumption; - int decoGasConsumption; - bool modWarning; - bool dynamicAscendRate; - bool graphicalSpeedIndicator; - bool alwaysShowppO2; - int tempSensorOffset; - unsigned safetyStopLength; - unsigned safetyStopStartDepth; - unsigned safetyStopEndDepth; - unsigned safetyStopResetDepth; -}; - - -#endif // DEVICEDETAILS_H diff --git a/subsurface-core/display.h b/subsurface-core/display.h deleted file mode 100644 index 9e3e1d159..000000000 --- a/subsurface-core/display.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef DISPLAY_H -#define DISPLAY_H - -#ifdef __cplusplus -extern "C" { -#endif - -struct membuffer; - -#define SCALE_SCREEN 1.0 -#define SCALE_PRINT (1.0 / get_screen_dpi()) - -extern double get_screen_dpi(void); - -/* Plot info with smoothing, velocity indication - * and one-, two- and three-minute minimums and maximums */ -struct plot_info { - int nr; - int maxtime; - int meandepth, maxdepth; - int minpressure, maxpressure; - int minhr, maxhr; - int mintemp, maxtemp; - enum {AIR, NITROX, TRIMIX, FREEDIVING} dive_type; - double endtempcoord; - double maxpp; - bool has_ndl; - struct plot_data *entry; -}; - -typedef enum { - SC_SCREEN, - SC_PRINT -} scale_mode_t; - -extern struct divecomputer *select_dc(struct dive *); - -extern unsigned int dc_number; - -extern unsigned int amount_selected; - -extern int is_default_dive_computer_device(const char *); -extern int is_default_dive_computer(const char *, const char *); - -typedef void (*device_callback_t)(const char *name, void *userdata); - -#define DC_TYPE_SERIAL 1 -#define DC_TYPE_UEMIS 2 -#define DC_TYPE_OTHER 3 - -int enumerate_devices(device_callback_t callback, void *userdata, int dc_type); - -extern const char *default_dive_computer_vendor; -extern const char *default_dive_computer_product; -extern const char *default_dive_computer_device; -extern int default_dive_computer_download_mode; -#define AMB_PERCENTAGE 50.0 - -#ifdef __cplusplus -} -#endif - -#endif // DISPLAY_H diff --git a/subsurface-core/dive.c b/subsurface-core/dive.c deleted file mode 100644 index ce730da28..000000000 --- a/subsurface-core/dive.c +++ /dev/null @@ -1,3561 +0,0 @@ -/* dive.c */ -/* maintains the internal dive list structure */ -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <limits.h> -#include "gettext.h" -#include "dive.h" -#include "libdivecomputer.h" -#include "device.h" -#include "divelist.h" -#include "qthelperfromc.h" - -/* one could argue about the best place to have this variable - - * it's used in the UI, but it seems to make the most sense to have it - * here */ -struct dive displayed_dive; -struct dive_site displayed_dive_site; - -struct tag_entry *g_tag_list = NULL; - -static const char *default_tags[] = { - QT_TRANSLATE_NOOP("gettextFromC", "boat"), QT_TRANSLATE_NOOP("gettextFromC", "shore"), QT_TRANSLATE_NOOP("gettextFromC", "drift"), - QT_TRANSLATE_NOOP("gettextFromC", "deep"), QT_TRANSLATE_NOOP("gettextFromC", "cavern"), QT_TRANSLATE_NOOP("gettextFromC", "ice"), - QT_TRANSLATE_NOOP("gettextFromC", "wreck"), QT_TRANSLATE_NOOP("gettextFromC", "cave"), QT_TRANSLATE_NOOP("gettextFromC", "altitude"), - QT_TRANSLATE_NOOP("gettextFromC", "pool"), QT_TRANSLATE_NOOP("gettextFromC", "lake"), QT_TRANSLATE_NOOP("gettextFromC", "river"), - QT_TRANSLATE_NOOP("gettextFromC", "night"), QT_TRANSLATE_NOOP("gettextFromC", "fresh"), QT_TRANSLATE_NOOP("gettextFromC", "student"), - QT_TRANSLATE_NOOP("gettextFromC", "instructor"), QT_TRANSLATE_NOOP("gettextFromC", "photo"), QT_TRANSLATE_NOOP("gettextFromC", "video"), - QT_TRANSLATE_NOOP("gettextFromC", "deco") -}; - -const char *cylinderuse_text[] = { - QT_TRANSLATE_NOOP("gettextFromC", "OC-gas"), QT_TRANSLATE_NOOP("gettextFromC", "diluent"), QT_TRANSLATE_NOOP("gettextFromC", "oxygen") -}; -const char *divemode_text[] = { "OC", "CCR", "PSCR", "Freedive" }; - -int event_is_gaschange(struct event *ev) -{ - return ev->type == SAMPLE_EVENT_GASCHANGE || - ev->type == SAMPLE_EVENT_GASCHANGE2; -} - -/* - * Does the gas mix data match the legacy - * libdivecomputer event format? If so, - * we can skip saving it, in order to maintain - * the old save formats. We'll re-generate the - * gas mix when loading. - */ -int event_gasmix_redundant(struct event *ev) -{ - int value = ev->value; - int o2, he; - - o2 = (value & 0xffff) * 10; - he = (value >> 16) * 10; - return o2 == ev->gas.mix.o2.permille && - he == ev->gas.mix.he.permille; -} - -struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name) -{ - int gas_index = -1; - struct event *ev, **p; - unsigned int size, len = strlen(name); - - size = sizeof(*ev) + len + 1; - ev = malloc(size); - if (!ev) - return NULL; - memset(ev, 0, size); - memcpy(ev->name, name, len); - ev->time.seconds = time; - ev->type = type; - ev->flags = flags; - ev->value = value; - - /* - * Expand the events into a sane format. Currently - * just gas switches - */ - switch (type) { - case SAMPLE_EVENT_GASCHANGE2: - /* High 16 bits are He percentage */ - ev->gas.mix.he.permille = (value >> 16) * 10; - - /* Extension to the GASCHANGE2 format: cylinder index in 'flags' */ - if (flags > 0 && flags <= MAX_CYLINDERS) - gas_index = flags-1; - /* Fallthrough */ - case SAMPLE_EVENT_GASCHANGE: - /* Low 16 bits are O2 percentage */ - ev->gas.mix.o2.permille = (value & 0xffff) * 10; - ev->gas.index = gas_index; - break; - } - - p = &dc->events; - - /* insert in the sorted list of events */ - while (*p && (*p)->time.seconds <= time) - p = &(*p)->next; - ev->next = *p; - *p = ev; - remember_event(name); - return ev; -} - -static int same_event(struct event *a, struct event *b) -{ - if (a->time.seconds != b->time.seconds) - return 0; - if (a->type != b->type) - return 0; - if (a->flags != b->flags) - return 0; - if (a->value != b->value) - return 0; - return !strcmp(a->name, b->name); -} - -void remove_event(struct event *event) -{ - struct event **ep = ¤t_dc->events; - while (ep && !same_event(*ep, event)) - ep = &(*ep)->next; - if (ep) { - /* we can't link directly with event->next - * because 'event' can be a copy from another - * dive (for instance the displayed_dive - * that we use on the interface to show things). */ - struct event *temp = (*ep)->next; - free(*ep); - *ep = temp; - } -} - -/* since the name is an array as part of the structure (how silly is that?) we - * have to actually remove the existing event and replace it with a new one. - * WARNING, WARNING... this may end up freeing event in case that event is indeed - * WARNING, WARNING... part of this divecomputer on this dive! */ -void update_event_name(struct dive *d, struct event *event, char *name) -{ - if (!d || !event) - return; - struct divecomputer *dc = get_dive_dc(d, dc_number); - if (!dc) - return; - struct event **removep = &dc->events; - struct event *remove; - while ((*removep)->next && !same_event(*removep, event)) - removep = &(*removep)->next; - if (!same_event(*removep, event)) - return; - remove = *removep; - *removep = (*removep)->next; - add_event(dc, event->time.seconds, event->type, event->flags, event->value, name); - free(remove); - invalidate_dive_cache(d); -} - -void add_extra_data(struct divecomputer *dc, const char *key, const char *value) -{ - struct extra_data **ed = &dc->extra_data; - - while (*ed) - ed = &(*ed)->next; - *ed = malloc(sizeof(struct extra_data)); - if (*ed) { - (*ed)->key = strdup(key); - (*ed)->value = strdup(value); - (*ed)->next = NULL; - } -} - -/* this returns a pointer to static variable - so use it right away after calling */ -struct gasmix *get_gasmix_from_event(struct event *ev) -{ - static struct gasmix dummy; - if (ev && event_is_gaschange(ev)) - return &ev->gas.mix; - - return &dummy; -} - -int get_pressure_units(int mb, const char **units) -{ - int pressure; - const char *unit; - struct units *units_p = get_units(); - - switch (units_p->pressure) { - case PASCAL: - pressure = mb * 100; - unit = translate("gettextFromC", "pascal"); - break; - case BAR: - default: - pressure = (mb + 500) / 1000; - unit = translate("gettextFromC", "bar"); - break; - case PSI: - pressure = mbar_to_PSI(mb); - unit = translate("gettextFromC", "psi"); - break; - } - if (units) - *units = unit; - return pressure; -} - -double get_temp_units(unsigned int mk, const char **units) -{ - double deg; - const char *unit; - struct units *units_p = get_units(); - - if (units_p->temperature == FAHRENHEIT) { - deg = mkelvin_to_F(mk); - unit = UTF8_DEGREE "F"; - } else { - deg = mkelvin_to_C(mk); - unit = UTF8_DEGREE "C"; - } - if (units) - *units = unit; - return deg; -} - -double get_volume_units(unsigned int ml, int *frac, const char **units) -{ - int decimals; - double vol; - const char *unit; - struct units *units_p = get_units(); - - switch (units_p->volume) { - case LITER: - default: - vol = ml / 1000.0; - unit = translate("gettextFromC", "â„“"); - decimals = 1; - break; - case CUFT: - vol = ml_to_cuft(ml); - unit = translate("gettextFromC", "cuft"); - decimals = 2; - break; - } - if (frac) - *frac = decimals; - if (units) - *units = unit; - return vol; -} - -int units_to_sac(double volume) -{ - if (get_units()->volume == CUFT) - return rint(cuft_to_l(volume) * 1000.0); - else - return rint(volume * 1000); -} - -unsigned int units_to_depth(double depth) -{ - if (get_units()->length == METERS) - return rint(depth * 1000); - return feet_to_mm(depth); -} - -double get_depth_units(int mm, int *frac, const char **units) -{ - int decimals; - double d; - const char *unit; - struct units *units_p = get_units(); - - switch (units_p->length) { - case METERS: - default: - d = mm / 1000.0; - unit = translate("gettextFromC", "m"); - decimals = d < 20; - break; - case FEET: - d = mm_to_feet(mm); - unit = translate("gettextFromC", "ft"); - decimals = 0; - break; - } - if (frac) - *frac = decimals; - if (units) - *units = unit; - return d; -} - -double get_vertical_speed_units(unsigned int mms, int *frac, const char **units) -{ - double d; - const char *unit; - const struct units *units_p = get_units(); - const double time_factor = units_p->vertical_speed_time == MINUTES ? 60.0 : 1.0; - - switch (units_p->length) { - case METERS: - default: - d = mms / 1000.0 * time_factor; - if (units_p->vertical_speed_time == MINUTES) - unit = translate("gettextFromC", "m/min"); - else - unit = translate("gettextFromC", "m/s"); - break; - case FEET: - d = mm_to_feet(mms) * time_factor; - if (units_p->vertical_speed_time == MINUTES) - unit = translate("gettextFromC", "ft/min"); - else - unit = translate("gettextFromC", "ft/s"); - break; - } - if (frac) - *frac = d < 10; - if (units) - *units = unit; - return d; -} - -double get_weight_units(unsigned int grams, int *frac, const char **units) -{ - int decimals; - double value; - const char *unit; - struct units *units_p = get_units(); - - if (units_p->weight == LBS) { - value = grams_to_lbs(grams); - unit = translate("gettextFromC", "lbs"); - decimals = 0; - } else { - value = grams / 1000.0; - unit = translate("gettextFromC", "kg"); - decimals = 1; - } - if (frac) - *frac = decimals; - if (units) - *units = unit; - return value; -} - -bool has_hr_data(struct divecomputer *dc) -{ - int i; - struct sample *sample; - - if (!dc) - return false; - - sample = dc->sample; - for (i = 0; i < dc->samples; i++) - if (sample[i].heartbeat) - return true; - return false; -} - -struct dive *alloc_dive(void) -{ - struct dive *dive; - - dive = malloc(sizeof(*dive)); - if (!dive) - exit(1); - memset(dive, 0, sizeof(*dive)); - dive->id = dive_getUniqID(dive); - return dive; -} - -static void free_dc(struct divecomputer *dc); -static void free_pic(struct picture *picture); - -/* this is very different from the copy_divecomputer later in this file; - * this function actually makes full copies of the content */ -static void copy_dc(struct divecomputer *sdc, struct divecomputer *ddc) -{ - *ddc = *sdc; - ddc->model = copy_string(sdc->model); - copy_samples(sdc, ddc); - copy_events(sdc, ddc); -} - -/* copy an element in a list of pictures */ -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 */ -static void copy_tl(struct tag_entry *st, struct tag_entry *dt) -{ - dt->tag = malloc(sizeof(struct divetag)); - dt->tag->name = copy_string(st->tag->name); - dt->tag->source = copy_string(st->tag->source); -} - -/* Clear everything but the first element; - * this works for taglist, picturelist, even dive computers */ -#define STRUCTURED_LIST_FREE(_type, _start, _free) \ - { \ - _type *_ptr = _start; \ - while (_ptr) { \ - _type *_next = _ptr->next; \ - _free(_ptr); \ - _ptr = _next; \ - } \ - } - -#define STRUCTURED_LIST_COPY(_type, _first, _dest, _cpy) \ - { \ - _type *_sptr = _first; \ - _type **_dptr = &_dest; \ - while (_sptr) { \ - *_dptr = malloc(sizeof(_type)); \ - _cpy(_sptr, *_dptr); \ - _sptr = _sptr->next; \ - _dptr = &(*_dptr)->next; \ - } \ - *_dptr = 0; \ - } - -/* copy_dive makes duplicates of many components of a dive; - * in order not to leak memory, we need to free those . - * copy_dive doesn't play with the divetrip and forward/backward pointers - * so we can ignore those */ -void clear_dive(struct dive *d) -{ - if (!d) - return; - /* free the strings */ - free(d->buddy); - free(d->divemaster); - free(d->notes); - free(d->suit); - /* free tags, additional dive computers, and pictures */ - taglist_free(d->tag_list); - STRUCTURED_LIST_FREE(struct divecomputer, d->dc.next, free_dc); - STRUCTURED_LIST_FREE(struct picture, d->picture_list, free_pic); - for (int i = 0; i < MAX_CYLINDERS; i++) - free((void *)d->cylinder[i].type.description); - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) - free((void *)d->weightsystem[i].description); - memset(d, 0, sizeof(struct dive)); -} - -/* make a true copy that is independent of the source dive; - * all data structures are duplicated, so the copy can be modified without - * any impact on the source */ -void copy_dive(struct dive *s, struct dive *d) -{ - clear_dive(d); - /* simply copy things over, but then make actual copies of the - * relevant components that are referenced through pointers, - * so all the strings and the structured lists */ - *d = *s; - invalidate_dive_cache(d); - d->buddy = copy_string(s->buddy); - d->divemaster = copy_string(s->divemaster); - d->notes = copy_string(s->notes); - d->suit = copy_string(s->suit); - for (int i = 0; i < MAX_CYLINDERS; i++) - d->cylinder[i].type.description = copy_string(s->cylinder[i].type.description); - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) - d->weightsystem[i].description = copy_string(s->weightsystem[i].description); - STRUCTURED_LIST_COPY(struct picture, s->picture_list, d->picture_list, copy_pl); - STRUCTURED_LIST_COPY(struct tag_entry, s->tag_list, d->tag_list, copy_tl); - - // Copy the first dc explicitly, then the list of subsequent dc's - copy_dc(&s->dc, &d->dc); - STRUCTURED_LIST_COPY(struct divecomputer, s->dc.next, d->dc.next, copy_dc); -} - -/* make a clone of the source dive and clean out the source dive; - * this is specifically so we can create a dive in the displayed_dive and then - * add it to the divelist. - * Note the difference to copy_dive() / clean_dive() */ -struct dive *clone_dive(struct dive *s) -{ - struct dive *dive = alloc_dive(); - *dive = *s; // so all the pointers in dive point to the things s pointed to - memset(s, 0, sizeof(struct dive)); // and now the pointers in s are gone - return dive; -} - -#define CONDITIONAL_COPY_STRING(_component) \ - if (what._component) \ - d->_component = copy_string(s->_component) - -// copy elements, depending on bits in what that are set -void selective_copy_dive(struct dive *s, struct dive *d, struct dive_components what, bool clear) -{ - if (clear) - clear_dive(d); - CONDITIONAL_COPY_STRING(notes); - CONDITIONAL_COPY_STRING(divemaster); - CONDITIONAL_COPY_STRING(buddy); - CONDITIONAL_COPY_STRING(suit); - if (what.rating) - d->rating = s->rating; - if (what.visibility) - d->visibility = s->visibility; - if (what.divesite) - d->dive_site_uuid = s->dive_site_uuid; - if (what.tags) - STRUCTURED_LIST_COPY(struct tag_entry, s->tag_list, d->tag_list, copy_tl); - if (what.cylinders) - copy_cylinders(s, d, false); - if (what.weights) - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) { - free((void *)d->weightsystem[i].description); - d->weightsystem[i] = s->weightsystem[i]; - d->weightsystem[i].description = copy_string(s->weightsystem[i].description); - } -} -#undef CONDITIONAL_COPY_STRING - -struct event *clone_event(const struct event *src_ev) -{ - struct event *ev; - if (!src_ev) - return NULL; - - size_t size = sizeof(*src_ev) + strlen(src_ev->name) + 1; - ev = (struct event*) malloc(size); - if (!ev) - exit(1); - memcpy(ev, src_ev, size); - ev->next = NULL; - - return ev; -} - -/* copies all events in this dive computer */ -void copy_events(struct divecomputer *s, struct divecomputer *d) -{ - struct event *ev, **pev; - if (!s || !d) - return; - ev = s->events; - pev = &d->events; - while (ev != NULL) { - struct event *new_ev = clone_event(ev); - *pev = new_ev; - pev = &new_ev->next; - ev = ev->next; - } - *pev = NULL; -} - -int nr_cylinders(struct dive *dive) -{ - int nr; - - for (nr = MAX_CYLINDERS; nr; --nr) { - cylinder_t *cylinder = dive->cylinder + nr - 1; - if (!cylinder_nodata(cylinder)) - break; - } - return nr; -} - -int nr_weightsystems(struct dive *dive) -{ - int nr; - - for (nr = MAX_WEIGHTSYSTEMS; nr; --nr) { - weightsystem_t *ws = dive->weightsystem + nr - 1; - if (!weightsystem_none(ws)) - break; - } - return nr; -} - -/* copy the equipment data part of the cylinders */ -void copy_cylinders(struct dive *s, struct dive *d, bool used_only) -{ - int i,j; - if (!s || !d) - return; - for (i = 0; i < MAX_CYLINDERS; i++) { - free((void *)d->cylinder[i].type.description); - memset(&d->cylinder[i], 0, sizeof(cylinder_t)); - } - for (i = j = 0; i < MAX_CYLINDERS; i++) { - if (!used_only || is_cylinder_used(s, i)) { - d->cylinder[j].type = s->cylinder[i].type; - d->cylinder[j].type.description = copy_string(s->cylinder[i].type.description); - d->cylinder[j].gasmix = s->cylinder[i].gasmix; - d->cylinder[j].depth = s->cylinder[i].depth; - d->cylinder[j].cylinder_use = s->cylinder[i].cylinder_use; - d->cylinder[j].manually_added = true; - j++; - } - } -} - -int cylinderuse_from_text(const char *text) -{ - for (enum cylinderuse i = 0; i < NUM_GAS_USE; i++) { - if (same_string(text, cylinderuse_text[i]) || same_string(text, translate("gettextFromC", cylinderuse_text[i]))) - return i; - } - return -1; -} - -void copy_samples(struct divecomputer *s, struct divecomputer *d) -{ - /* instead of carefully copying them one by one and calling add_sample - * over and over again, let's just copy the whole blob */ - if (!s || !d) - return; - int nr = s->samples; - d->samples = nr; - d->alloc_samples = nr; - // We expect to be able to read the memory in the other end of the pointer - // if its a valid pointer, so don't expect malloc() to return NULL for - // zero-sized malloc, do it ourselves. - d->sample = NULL; - - if(!nr) - return; - - d->sample = malloc(nr * sizeof(struct sample)); - if (d->sample) - memcpy(d->sample, s->sample, nr * sizeof(struct sample)); -} - -struct sample *prepare_sample(struct divecomputer *dc) -{ - if (dc) { - int nr = dc->samples; - int alloc_samples = dc->alloc_samples; - struct sample *sample; - if (nr >= alloc_samples) { - struct sample *newsamples; - - alloc_samples = (alloc_samples * 3) / 2 + 10; - newsamples = realloc(dc->sample, alloc_samples * sizeof(struct sample)); - if (!newsamples) - return NULL; - dc->alloc_samples = alloc_samples; - dc->sample = newsamples; - } - sample = dc->sample + nr; - memset(sample, 0, sizeof(*sample)); - return sample; - } - return NULL; -} - -void finish_sample(struct divecomputer *dc) -{ - dc->samples++; -} - -/* - * So when we re-calculate maxdepth and meandepth, we will - * not override the old numbers if they are close to the - * new ones. - * - * Why? Because a dive computer may well actually track the - * max depth and mean depth at finer granularity than the - * samples it stores. So it's possible that the max and mean - * have been reported more correctly originally. - * - * Only if the values calculated from the samples are clearly - * different do we override the normal depth values. - * - * This considers 1m to be "clearly different". That's - * a totally random number. - */ -static void update_depth(depth_t *depth, int new) -{ - if (new) { - int old = depth->mm; - - if (abs(old - new) > 1000) - depth->mm = new; - } -} - -static void update_temperature(temperature_t *temperature, int new) -{ - if (new) { - int old = temperature->mkelvin; - - if (abs(old - new) > 1000) - temperature->mkelvin = new; - } -} - -/* - * Calculate how long we were actually under water, and the average - * depth while under water. - * - * This ignores any surface time in the middle of the dive. - */ -void fixup_dc_duration(struct divecomputer *dc) -{ - int duration, i; - int lasttime, lastdepth, depthtime; - - duration = 0; - lasttime = 0; - lastdepth = 0; - depthtime = 0; - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int time = sample->time.seconds; - int depth = sample->depth.mm; - - /* We ignore segments at the surface */ - if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) { - duration += time - lasttime; - depthtime += (time - lasttime) * (depth + lastdepth) / 2; - } - lastdepth = depth; - lasttime = time; - } - if (duration) { - dc->duration.seconds = duration; - dc->meandepth.mm = (depthtime + duration / 2) / duration; - } -} - -void per_cylinder_mean_depth(struct dive *dive, struct divecomputer *dc, int *mean, int *duration) -{ - int i; - int depthtime[MAX_CYLINDERS] = { 0, }; - uint32_t lasttime = 0; - int lastdepth = 0; - int idx = 0; - - for (i = 0; i < MAX_CYLINDERS; i++) - mean[i] = duration[i] = 0; - if (!dc) - return; - struct event *ev = get_next_event(dc->events, "gaschange"); - if (!ev || (dc && dc->sample && ev->time.seconds == dc->sample[0].time.seconds && get_next_event(ev->next, "gaschange") == NULL)) { - // we have either no gas change or only one gas change and that's setting an explicit first cylinder - mean[explicit_first_cylinder(dive, dc)] = dc->meandepth.mm; - duration[explicit_first_cylinder(dive, dc)] = dc->duration.seconds; - - if (dc->divemode == CCR) { - // Do the same for the O2 cylinder - int o2_cyl = get_cylinder_idx_by_use(dive, OXYGEN); - if (o2_cyl < 0) - return; - mean[o2_cyl] = dc->meandepth.mm; - duration[o2_cyl] = dc->duration.seconds; - } - return; - } - if (!dc->samples) - dc = fake_dc(dc, false); - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - uint32_t time = sample->time.seconds; - int depth = sample->depth.mm; - - /* Make sure to move the event past 'lasttime' */ - while (ev && lasttime >= ev->time.seconds) { - idx = get_cylinder_index(dive, ev); - ev = get_next_event(ev->next, "gaschange"); - } - - /* Do we need to fake a midway sample at an event? */ - if (ev && time > ev->time.seconds) { - int newtime = ev->time.seconds; - int newdepth = interpolate(lastdepth, depth, newtime - lasttime, time - lasttime); - - time = newtime; - depth = newdepth; - i--; - } - /* We ignore segments at the surface */ - if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) { - duration[idx] += time - lasttime; - depthtime[idx] += (time - lasttime) * (depth + lastdepth) / 2; - } - lastdepth = depth; - lasttime = time; - } - for (i = 0; i < MAX_CYLINDERS; i++) { - if (duration[i]) - mean[i] = (depthtime[i] + duration[i] / 2) / duration[i]; - } -} - -static void update_min_max_temperatures(struct dive *dive, temperature_t temperature) -{ - if (temperature.mkelvin) { - if (!dive->maxtemp.mkelvin || temperature.mkelvin > dive->maxtemp.mkelvin) - dive->maxtemp = temperature; - if (!dive->mintemp.mkelvin || temperature.mkelvin < dive->mintemp.mkelvin) - dive->mintemp = temperature; - } -} - -int gas_volume(cylinder_t *cyl, pressure_t p) -{ - double bar = p.mbar / 1000.0; - double z_factor = gas_compressibility_factor(&cyl->gasmix, bar); - return cyl->type.size.mliter * bar_to_atm(bar) / z_factor; -} - -/* - * If the cylinder tank pressures are within half a bar - * (about 8 PSI) of the sample pressures, we consider it - * to be a rounding error, and throw them away as redundant. - */ -static int same_rounded_pressure(pressure_t a, pressure_t b) -{ - return abs(a.mbar - b.mbar) <= 500; -} - -/* Some dive computers (Cobalt) don't start the dive with cylinder 0 but explicitly - * tell us what the first gas is with a gas change event in the first sample. - * Sneakily we'll use a return value of 0 (or FALSE) when there is no explicit - * first cylinder - in which case cylinder 0 is indeed the first cylinder */ -int explicit_first_cylinder(struct dive *dive, struct divecomputer *dc) -{ - if (dc) { - struct event *ev = get_next_event(dc->events, "gaschange"); - if (ev && dc->sample && ev->time.seconds == dc->sample[0].time.seconds) - return get_cylinder_index(dive, ev); - else if (dc->divemode == CCR) - return MAX(get_cylinder_idx_by_use(dive, DILUENT), 0); - } - return 0; -} - -/* this gets called when the dive mode has changed (so OC vs. CC) - * there are two places we might have setpoints... events or in the samples - */ -void update_setpoint_events(struct divecomputer *dc) -{ - struct event *ev; - int new_setpoint = 0; - - if (dc->divemode == CCR) - new_setpoint = prefs.defaultsetpoint; - - if (dc->divemode == OC && - (same_string(dc->model, "Shearwater Predator") || - same_string(dc->model, "Shearwater Petrel") || - same_string(dc->model, "Shearwater Nerd"))) { - // make sure there's no setpoint in the samples - // this is an irreversible change - so switching a dive to OC - // by mistake when it's actually CCR is _bad_ - // So we make sure, this comes from a Predator or Petrel and we only remove - // pO2 values we would have computed anyway. - struct event *ev = get_next_event(dc->events, "gaschange"); - struct gasmix *gasmix = get_gasmix_from_event(ev); - struct event *next = get_next_event(ev, "gaschange"); - - for (int i = 0; i < dc->samples; i++) { - struct gas_pressures pressures; - if (next && dc->sample[i].time.seconds >= next->time.seconds) { - ev = next; - gasmix = get_gasmix_from_event(ev); - next = get_next_event(ev, "gaschange"); - } - fill_pressures(&pressures, calculate_depth_to_mbar(dc->sample[i].depth.mm, dc->surface_pressure, 0), gasmix ,0, OC); - if (abs(dc->sample[i].setpoint.mbar - (int)(1000 * pressures.o2)) <= 50) - dc->sample[i].setpoint.mbar = 0; - } - } - - // an "SP change" event at t=0 is currently our marker for OC vs CCR - // this will need to change to a saner setup, but for now we can just - // check if such an event is there and adjust it, or add that event - ev = get_next_event(dc->events, "SP change"); - if (ev && ev->time.seconds == 0) { - ev->value = new_setpoint; - } else { - if (!add_event(dc, 0, SAMPLE_EVENT_PO2, 0, new_setpoint, "SP change")) - fprintf(stderr, "Could not add setpoint change event\n"); - } -} - -void sanitize_gasmix(struct gasmix *mix) -{ - unsigned int o2, he; - - o2 = mix->o2.permille; - he = mix->he.permille; - - /* Regular air: leave empty */ - if (!he) { - if (!o2) - return; - /* 20.8% to 21% O2 is just air */ - if (gasmix_is_air(mix)) { - mix->o2.permille = 0; - return; - } - } - - /* Sane mix? */ - if (o2 <= 1000 && he <= 1000 && o2 + he <= 1000) - return; - fprintf(stderr, "Odd gasmix: %u O2 %u He\n", o2, he); - memset(mix, 0, sizeof(*mix)); -} - -/* - * See if the size/workingpressure looks like some standard cylinder - * size, eg "AL80". - * - * NOTE! We don't take compressibility into account when naming - * cylinders. That makes a certain amount of sense, since the - * cylinder name is independent from the gasmix, and different - * gasmixes have different compressibility. - */ -static void match_standard_cylinder(cylinder_type_t *type) -{ - double cuft, bar; - int psi, len; - const char *fmt; - char buffer[40], *p; - - /* Do we already have a cylinder description? */ - if (type->description) - return; - - bar = type->workingpressure.mbar / 1000.0; - cuft = ml_to_cuft(type->size.mliter); - cuft *= bar_to_atm(bar); - psi = to_PSI(type->workingpressure); - - switch (psi) { - case 2300 ... 2500: /* 2400 psi: LP tank */ - fmt = "LP%d"; - break; - case 2600 ... 2700: /* 2640 psi: LP+10% */ - fmt = "LP%d"; - break; - case 2900 ... 3100: /* 3000 psi: ALx tank */ - fmt = "AL%d"; - break; - case 3400 ... 3500: /* 3442 psi: HP tank */ - fmt = "HP%d"; - break; - case 3700 ... 3850: /* HP+10% */ - fmt = "HP%d+"; - break; - default: - return; - } - len = snprintf(buffer, sizeof(buffer), fmt, (int)rint(cuft)); - p = malloc(len + 1); - if (!p) - return; - memcpy(p, buffer, len + 1); - type->description = p; -} - - -/* - * There are two ways to give cylinder size information: - * - total amount of gas in cuft (depends on working pressure and physical size) - * - physical size - * - * where "physical size" is the one that actually matters and is sane. - * - * We internally use physical size only. But we save the workingpressure - * so that we can do the conversion if required. - */ -static void sanitize_cylinder_type(cylinder_type_t *type) -{ - double volume_of_air, volume; - - /* If we have no working pressure, it had *better* be just a physical size! */ - if (!type->workingpressure.mbar) - return; - - /* No size either? Nothing to go on */ - if (!type->size.mliter) - return; - - if (xml_parsing_units.volume == CUFT) { - double bar = type->workingpressure.mbar / 1000.0; - /* confusing - we don't really start from ml but millicuft !*/ - volume_of_air = cuft_to_l(type->size.mliter); - /* milliliters at 1 atm: not corrected for compressibility! */ - volume = volume_of_air / bar_to_atm(bar); - type->size.mliter = rint(volume); - } - - /* Ok, we have both size and pressure: try to match a description */ - match_standard_cylinder(type); -} - -static void sanitize_cylinder_info(struct dive *dive) -{ - int i; - - for (i = 0; i < MAX_CYLINDERS; i++) { - sanitize_gasmix(&dive->cylinder[i].gasmix); - sanitize_cylinder_type(&dive->cylinder[i].type); - } -} - -/* some events should never be thrown away */ -static bool is_potentially_redundant(struct event *event) -{ - if (!strcmp(event->name, "gaschange")) - return false; - if (!strcmp(event->name, "bookmark")) - return false; - if (!strcmp(event->name, "heading")) - return false; - return true; -} - -/* match just by name - we compare the details in the code that uses this helper */ -static struct event *find_previous_event(struct divecomputer *dc, struct event *event) -{ - struct event *ev = dc->events; - struct event *previous = NULL; - - if (same_string(event->name, "")) - return NULL; - while (ev && ev != event) { - if (same_string(ev->name, event->name)) - previous = ev; - ev = ev->next; - } - return previous; -} - -static void fixup_surface_pressure(struct dive *dive) -{ - struct divecomputer *dc; - int sum = 0, nr = 0; - - for_each_dc (dive, dc) { - if (dc->surface_pressure.mbar) { - sum += dc->surface_pressure.mbar; - nr++; - } - } - if (nr) - dive->surface_pressure.mbar = (sum + nr / 2) / nr; -} - -static void fixup_water_salinity(struct dive *dive) -{ - struct divecomputer *dc; - int sum = 0, nr = 0; - - for_each_dc (dive, dc) { - if (dc->salinity) { - if (dc->salinity < 500) - dc->salinity += FRESHWATER_SALINITY; - sum += dc->salinity; - nr++; - } - } - if (nr) - dive->salinity = (sum + nr / 2) / nr; -} - -static void fixup_meandepth(struct dive *dive) -{ - struct divecomputer *dc; - int sum = 0, nr = 0; - - for_each_dc (dive, dc) { - if (dc->meandepth.mm) { - sum += dc->meandepth.mm; - nr++; - } - } - if (nr) - dive->meandepth.mm = (sum + nr / 2) / nr; -} - -static void fixup_duration(struct dive *dive) -{ - struct divecomputer *dc; - unsigned int duration = 0; - - for_each_dc (dive, dc) - duration = MAX(duration, dc->duration.seconds); - - dive->duration.seconds = duration; -} - -/* - * What do the dive computers say the water temperature is? - * (not in the samples, but as dc property for dcs that support that) - */ -unsigned int dc_watertemp(struct divecomputer *dc) -{ - int sum = 0, nr = 0; - - do { - if (dc->watertemp.mkelvin) { - sum += dc->watertemp.mkelvin; - nr++; - } - } while ((dc = dc->next) != NULL); - if (!nr) - return 0; - return (sum + nr / 2) / nr; -} - -static void fixup_watertemp(struct dive *dive) -{ - if (!dive->watertemp.mkelvin) - dive->watertemp.mkelvin = dc_watertemp(&dive->dc); -} - -/* - * What do the dive computers say the air temperature is? - */ -unsigned int dc_airtemp(struct divecomputer *dc) -{ - int sum = 0, nr = 0; - - do { - if (dc->airtemp.mkelvin) { - sum += dc->airtemp.mkelvin; - nr++; - } - } while ((dc = dc->next) != NULL); - if (!nr) - return 0; - return (sum + nr / 2) / nr; -} - -static void fixup_cylinder_use(struct dive *dive) // for CCR dives, store the indices -{ // of the oxygen and diluent cylinders - dive->oxygen_cylinder_index = get_cylinder_idx_by_use(dive, OXYGEN); - dive->diluent_cylinder_index = get_cylinder_idx_by_use(dive, DILUENT); -} - -static void fixup_airtemp(struct dive *dive) -{ - if (!dive->airtemp.mkelvin) - dive->airtemp.mkelvin = dc_airtemp(&dive->dc); -} - -/* zero out the airtemp in the dive structure if it was just created by - * running fixup on the dive. keep it if it had been edited by hand */ -static void un_fixup_airtemp(struct dive *a) -{ - if (a->airtemp.mkelvin && a->airtemp.mkelvin == dc_airtemp(&a->dc)) - a->airtemp.mkelvin = 0; -} - -/* - * events are stored as a linked list, so the concept of - * "consecutive, identical events" is somewhat hard to - * implement correctly (especially given that on some dive - * computers events are asynchronous, so they can come in - * between what would be the non-constant sample rate). - * - * So what we do is that we throw away clearly redundant - * events that are fewer than 61 seconds apart (assuming there - * is no dive computer with a sample rate of more than 60 - * seconds... that would be pretty pointless to plot the - * profile with) - * - * We first only mark the events for deletion so that we - * still know when the previous event happened. - */ -static void fixup_dc_events(struct divecomputer *dc) -{ - struct event *event; - - event = dc->events; - while (event) { - struct event *prev; - if (is_potentially_redundant(event)) { - prev = find_previous_event(dc, event); - if (prev && prev->value == event->value && - prev->flags == event->flags && - event->time.seconds - prev->time.seconds < 61) - event->deleted = true; - } - event = event->next; - } - event = dc->events; - while (event) { - if (event->next && event->next->deleted) { - struct event *nextnext = event->next->next; - free(event->next); - event->next = nextnext; - } else { - event = event->next; - } - } -} - -static int interpolate_depth(struct divecomputer *dc, int idx, int lastdepth, int lasttime, int now) -{ - int i; - int nextdepth = lastdepth; - int nexttime = now; - - for (i = idx+1; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - if (sample->depth.mm < 0) - continue; - nextdepth = sample->depth.mm; - nexttime = sample->time.seconds; - break; - } - return interpolate(lastdepth, nextdepth, now-lasttime, nexttime-lasttime); -} - -static void fixup_dc_depths(struct dive *dive, struct divecomputer *dc) -{ - int i; - int maxdepth = dc->maxdepth.mm; - int lasttime = 0, lastdepth = 0; - - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int time = sample->time.seconds; - int depth = sample->depth.mm; - - if (depth < 0) { - depth = interpolate_depth(dc, i, lastdepth, lasttime, time); - sample->depth.mm = depth; - } - - if (depth > SURFACE_THRESHOLD) { - if (depth > maxdepth) - maxdepth = depth; - } - - lastdepth = depth; - lasttime = time; - if (sample->cns > dive->maxcns) - dive->maxcns = sample->cns; - } - - update_depth(&dc->maxdepth, maxdepth); - if (maxdepth > dive->maxdepth.mm) - dive->maxdepth.mm = maxdepth; -} - -static void fixup_dc_temp(struct dive *dive, struct divecomputer *dc) -{ - int i; - int mintemp = 0, lasttemp = 0; - - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int temp = sample->temperature.mkelvin; - - if (temp) { - /* - * If we have consecutive identical - * temperature readings, throw away - * the redundant ones. - */ - if (lasttemp == temp) - sample->temperature.mkelvin = 0; - else - lasttemp = temp; - - if (!mintemp || temp < mintemp) - mintemp = temp; - } - - update_min_max_temperatures(dive, sample->temperature); - } - update_temperature(&dc->watertemp, mintemp); - update_min_max_temperatures(dive, dc->watertemp); -} - -/* - * Fix up cylinder sensor information in the samples if we have - * an explicit first cylinder - */ -static void fixup_dc_cylinder_index(struct dive *dive, struct divecomputer *dc) -{ - int i; - int first_cylinder = explicit_first_cylinder(dive, dc); - - if (!first_cylinder) - return; - - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - - if (sample->sensor == 0) - sample->sensor = first_cylinder; - } -} - -/* - * Simplify dc pressure information: - * (a) Remove redundant pressure information - * (b) Remove linearly interpolated pressure data - */ -static void simplify_dc_pressures(struct dive *dive, struct divecomputer *dc) -{ - int i, j; - int lastindex = -1; - int lastpressure = 0, lasto2pressure = 0; - int pressure_delta[MAX_CYLINDERS] = { INT_MAX, }; - - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int pressure = sample->cylinderpressure.mbar; - int o2_pressure = sample->o2cylinderpressure.mbar; - int index; - - index = sample->sensor; - - if (index == lastindex) { - /* Remove duplicate redundant pressure information */ - if (pressure == lastpressure) - sample->cylinderpressure.mbar = 0; - if (o2_pressure == lasto2pressure) - sample->o2cylinderpressure.mbar = 0; - /* check for simply linear data in the samples - +INT_MAX means uninitialized, -INT_MAX means not linear */ - if (pressure_delta[index] != -INT_MAX && lastpressure) { - if (pressure_delta[index] == INT_MAX) { - pressure_delta[index] = abs(pressure - lastpressure); - } else { - int cur_delta = abs(pressure - lastpressure); - if (cur_delta && abs(cur_delta - pressure_delta[index]) > 150) { - /* ok the samples aren't just a linearisation - * between start and end */ - pressure_delta[index] = -INT_MAX; - } - } - } - } - lastindex = index; - lastpressure = pressure; - lasto2pressure = o2_pressure; - } - - /* if all the samples for a cylinder have pressure data that - * is basically equidistant throw out the sample cylinder pressure - * information but make sure we still have a valid start and end - * pressure - * this happens when DivingLog decides to linearalize the - * pressure between beginning and end and for strange reasons - * decides to put that in the sample data as if it came from - * the dive computer; we don't want that (we'll visualize with - * constant SAC rate instead) - * WARNING WARNING - I have only seen this in single tank dives - * --- maybe I should try to create a multi tank dive and see what - * --- divinglog does there - but the code right now is only tested - * --- for the single tank case */ - for (j = 0; j < MAX_CYLINDERS; j++) { - if (abs(pressure_delta[j]) != INT_MAX) { - cylinder_t *cyl = dive->cylinder + j; - for (i = 0; i < dc->samples; i++) - if (dc->sample[i].sensor == j) - dc->sample[i].cylinderpressure.mbar = 0; - if (!cyl->start.mbar) - cyl->start.mbar = cyl->sample_start.mbar; - if (!cyl->end.mbar) - cyl->end.mbar = cyl->sample_end.mbar; - cyl->sample_start.mbar = 0; - cyl->sample_end.mbar = 0; - } - } -} - -/* FIXME! sensor -> cylinder mapping? */ -static void fixup_start_pressure(struct dive *dive, int idx, pressure_t p) -{ - if (idx >= 0 && idx < MAX_CYLINDERS) { - cylinder_t *cyl = dive->cylinder + idx; - if (p.mbar && !cyl->sample_start.mbar) - cyl->sample_start = p; - } -} - -static void fixup_end_pressure(struct dive *dive, int idx, pressure_t p) -{ - if (idx >= 0 && idx < MAX_CYLINDERS) { - cylinder_t *cyl = dive->cylinder + idx; - if (p.mbar && !cyl->sample_end.mbar) - cyl->sample_end = p; - } -} - -/* - * Check the cylinder pressure sample information and fill in the - * overall cylinder pressures from those. - * - * We ignore surface samples for tank pressure information. - * - * At the beginning of the dive, let the cylinder cool down - * if the diver starts off at the surface. And at the end - * of the dive, there may be surface pressures where the - * diver has already turned off the air supply (especially - * for computers like the Uemis Zurich that end up saving - * quite a bit of samples after the dive has ended). - */ -static void fixup_dive_pressures(struct dive *dive, struct divecomputer *dc) -{ - int i, o2index = -1; - - if (dive->dc.divemode == CCR) - o2index = get_cylinder_idx_by_use(dive, OXYGEN); - - /* Walk the samples from the beginning to find starting pressures.. */ - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - - if (sample->depth.mm < SURFACE_THRESHOLD) - continue; - - fixup_start_pressure(dive, sample->sensor, sample->cylinderpressure); - fixup_start_pressure(dive, o2index, sample->o2cylinderpressure); - } - - /* ..and from the end for ending pressures */ - for (i = dc->samples; --i >= 0; ) { - struct sample *sample = dc->sample + i; - - if (sample->depth.mm < SURFACE_THRESHOLD) - continue; - - fixup_end_pressure(dive, sample->sensor, sample->cylinderpressure); - fixup_end_pressure(dive, o2index, sample->o2cylinderpressure); - } - - simplify_dc_pressures(dive, dc); -} - -static void fixup_dive_dc(struct dive *dive, struct divecomputer *dc) -{ - int i; - - /* Add device information to table */ - if (dc->deviceid && (dc->serial || dc->fw_version)) - create_device_node(dc->model, dc->deviceid, dc->serial, dc->fw_version, ""); - - /* Fixup duration and mean depth */ - fixup_dc_duration(dc); - - /* Fix up sample depth data */ - fixup_dc_depths(dive, dc); - - /* Fix up dive temperatures based on dive computer samples */ - fixup_dc_temp(dive, dc); - - /* Fix up cylinder sensor data */ - fixup_dc_cylinder_index(dive, dc); - - /* Fix up cylinder pressures based on DC info */ - fixup_dive_pressures(dive, dc); - - fixup_dc_events(dc); -} - -struct dive *fixup_dive(struct dive *dive) -{ - int i; - struct divecomputer *dc; - - sanitize_cylinder_info(dive); - dive->maxcns = dive->cns; - - /* - * Use the dive's temperatures for minimum and maximum in case - * we do not have temperatures recorded by DC. - */ - - update_min_max_temperatures(dive, dive->watertemp); - - for_each_dc (dive, dc) - fixup_dive_dc(dive, dc); - - fixup_water_salinity(dive); - fixup_surface_pressure(dive); - fixup_meandepth(dive); - fixup_duration(dive); - fixup_watertemp(dive); - fixup_airtemp(dive); - fixup_cylinder_use(dive); // store indices for CCR oxygen and diluent cylinders - for (i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = dive->cylinder + i; - add_cylinder_description(&cyl->type); - if (same_rounded_pressure(cyl->sample_start, cyl->start)) - cyl->start.mbar = 0; - if (same_rounded_pressure(cyl->sample_end, cyl->end)) - cyl->end.mbar = 0; - } - update_cylinder_related_info(dive); - for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) { - weightsystem_t *ws = dive->weightsystem + i; - add_weightsystem_description(ws); - } - /* we should always have a uniq ID as that gets assigned during alloc_dive(), - * but we want to make sure... */ - if (!dive->id) - dive->id = dive_getUniqID(dive); - - return dive; -} - -/* Don't pick a zero for MERGE_MIN() */ -#define MERGE_MAX(res, a, b, n) res->n = MAX(a->n, b->n) -#define MERGE_MIN(res, a, b, n) res->n = (a->n) ? (b->n) ? MIN(a->n, b->n) : (a->n) : (b->n) -#define MERGE_TXT(res, a, b, n) res->n = merge_text(a->n, b->n) -#define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n - -struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc) -{ - struct sample *p = prepare_sample(dc); - - if (p) { - *p = *sample; - p->time.seconds = time; - finish_sample(dc); - } - return p; -} - -/* - * This is like add_sample(), but if the distance from the last sample - * is excessive, we add two surface samples in between. - * - * This is so that if you merge two non-overlapping dives, we make sure - * that the time in between the dives is at the surface, not some "last - * sample that happened to be at a depth of 1.2m". - */ -static void merge_one_sample(struct sample *sample, int time, struct divecomputer *dc) -{ - int last = dc->samples - 1; - if (last >= 0) { - static struct sample surface; - struct sample *prev = dc->sample + last; - int last_time = prev->time.seconds; - int last_depth = prev->depth.mm; - - /* - * Only do surface events if the samples are more than - * a minute apart, and shallower than 5m - */ - if (time > last_time + 60 && last_depth < 5000) { - add_sample(&surface, last_time + 20, dc); - add_sample(&surface, time - 20, dc); - } - } - add_sample(sample, time, dc); -} - - -/* - * Merge samples. Dive 'a' is "offset" seconds before Dive 'b' - */ -static void merge_samples(struct divecomputer *res, struct divecomputer *a, struct divecomputer *b, int offset) -{ - int asamples = a->samples; - int bsamples = b->samples; - struct sample *as = a->sample; - struct sample *bs = b->sample; - - /* - * We want a positive sample offset, so that sample - * times are always positive. So if the samples for - * 'b' are before the samples for 'a' (so the offset - * is negative), we switch a and b around, and use - * the reverse offset. - */ - if (offset < 0) { - offset = -offset; - asamples = bsamples; - bsamples = a->samples; - as = bs; - bs = a->sample; - } - - for (;;) { - int at, bt; - struct sample sample; - - if (!res) - return; - - at = asamples ? as->time.seconds : -1; - bt = bsamples ? bs->time.seconds + offset : -1; - - /* No samples? All done! */ - if (at < 0 && bt < 0) - return; - - /* Only samples from a? */ - if (bt < 0) { - add_sample_a: - merge_one_sample(as, at, res); - as++; - asamples--; - continue; - } - - /* Only samples from b? */ - if (at < 0) { - add_sample_b: - merge_one_sample(bs, bt, res); - bs++; - bsamples--; - continue; - } - - if (at < bt) - goto add_sample_a; - if (at > bt) - goto add_sample_b; - - /* same-time sample: add a merged sample. Take the non-zero ones */ - sample = *bs; - if (as->depth.mm) - sample.depth = as->depth; - if (as->temperature.mkelvin) - sample.temperature = as->temperature; - if (as->cylinderpressure.mbar) - sample.cylinderpressure = as->cylinderpressure; - if (as->sensor) - sample.sensor = as->sensor; - if (as->cns) - sample.cns = as->cns; - if (as->setpoint.mbar) - sample.setpoint = as->setpoint; - if (as->ndl.seconds) - sample.ndl = as->ndl; - if (as->stoptime.seconds) - sample.stoptime = as->stoptime; - if (as->stopdepth.mm) - sample.stopdepth = as->stopdepth; - if (as->in_deco) - sample.in_deco = true; - - merge_one_sample(&sample, at, res); - - as++; - bs++; - asamples--; - bsamples--; - } -} - -static char *merge_text(const char *a, const char *b) -{ - char *res; - if (!a && !b) - return NULL; - if (!a || !*a) - return copy_string(b); - if (!b || !*b) - return strdup(a); - if (!strcmp(a, b)) - return copy_string(a); - res = malloc(strlen(a) + strlen(b) + 32); - if (!res) - return (char *)a; - sprintf(res, translate("gettextFromC", "(%s) or (%s)"), a, b); - return res; -} - -#define SORT(a, b, field) \ - if (a->field != b->field) \ - return a->field < b->field ? -1 : 1 - -static int sort_event(struct event *a, struct event *b) -{ - SORT(a, b, time.seconds); - SORT(a, b, type); - SORT(a, b, flags); - SORT(a, b, value); - return strcmp(a->name, b->name); -} - -static void merge_events(struct divecomputer *res, struct divecomputer *src1, struct divecomputer *src2, int offset) -{ - struct event *a, *b; - struct event **p = &res->events; - - /* Always use positive offsets */ - if (offset < 0) { - struct divecomputer *tmp; - - offset = -offset; - tmp = src1; - src1 = src2; - src2 = tmp; - } - - a = src1->events; - b = src2->events; - while (b) { - b->time.seconds += offset; - b = b->next; - } - b = src2->events; - - while (a || b) { - int s; - if (!b) { - *p = a; - break; - } - if (!a) { - *p = b; - break; - } - s = sort_event(a, b); - /* Pick b */ - if (s > 0) { - *p = b; - p = &b->next; - b = b->next; - continue; - } - /* Pick 'a' or neither */ - if (s < 0) { - *p = a; - p = &a->next; - } - a = a->next; - continue; - } -} - -/* Pick whichever has any info (if either). Prefer 'a' */ -static void merge_cylinder_type(cylinder_type_t *src, cylinder_type_t *dst) -{ - if (!dst->size.mliter) - dst->size.mliter = src->size.mliter; - if (!dst->workingpressure.mbar) - dst->workingpressure.mbar = src->workingpressure.mbar; - if (!dst->description) { - dst->description = src->description; - src->description = NULL; - } -} - -static void merge_cylinder_mix(struct gasmix *src, struct gasmix *dst) -{ - if (!dst->o2.permille) - *dst = *src; -} - -static void merge_cylinder_info(cylinder_t *src, cylinder_t *dst) -{ - merge_cylinder_type(&src->type, &dst->type); - merge_cylinder_mix(&src->gasmix, &dst->gasmix); - MERGE_MAX(dst, dst, src, start.mbar); - MERGE_MIN(dst, dst, src, end.mbar); -} - -static void merge_weightsystem_info(weightsystem_t *res, weightsystem_t *a, weightsystem_t *b) -{ - if (!a->weight.grams) - a = b; - *res = *a; -} - -/* get_cylinder_idx_by_use(): Find the index of the first cylinder with a particular CCR use type. - * The index returned corresponds to that of the first cylinder with a cylinder_use that - * equals the appropriate enum value [oxygen, diluent, bailout] given by cylinder_use_type. - * A negative number returned indicates that a match could not be found. - * Call parameters: dive = the dive being processed - * cylinder_use_type = an enum, one of {oxygen, diluent, bailout} */ -extern int get_cylinder_idx_by_use(struct dive *dive, enum cylinderuse cylinder_use_type) -{ - int cylinder_index; - for (cylinder_index = 0; cylinder_index < MAX_CYLINDERS; cylinder_index++) { - if (dive->cylinder[cylinder_index].cylinder_use == cylinder_use_type) - return cylinder_index; // return the index of the cylinder with that cylinder use type - } - return -1; // negative number means cylinder_use_type not found in list of cylinders -} - -int gasmix_distance(const struct gasmix *a, const struct gasmix *b) -{ - int a_o2 = get_o2(a), b_o2 = get_o2(b); - int a_he = get_he(a), b_he = get_he(b); - int delta_o2 = a_o2 - b_o2, delta_he = a_he - b_he; - - delta_he = delta_he * delta_he; - delta_o2 = delta_o2 * delta_o2; - return delta_he + delta_o2; -} - -/* fill_pressures(): Compute partial gas pressures in bar from gasmix and ambient pressures, possibly for OC or CCR, to be - * extended to PSCT. This function does the calculations of gas pressures applicable to a single point on the dive profile. - * The structure "pressures" is used to return calculated gas pressures to the calling software. - * Call parameters: po2 = po2 value applicable to the record in calling function - * amb_pressure = ambient pressure applicable to the record in calling function - * *pressures = structure for communicating o2 sensor values from and gas pressures to the calling function. - * *mix = structure containing cylinder gas mixture information. - * This function called by: calculate_gas_information_new() in profile.c; add_segment() in deco.c. - */ -extern void fill_pressures(struct gas_pressures *pressures, const double amb_pressure, const struct gasmix *mix, double po2, enum dive_comp_type divemode) -{ - if (po2) { // This is probably a CCR dive where pressures->o2 is defined - if (po2 >= amb_pressure) { - pressures->o2 = amb_pressure; - pressures->n2 = pressures->he = 0.0; - } else { - pressures->o2 = po2; - if (get_o2(mix) == 1000) { - pressures->he = pressures->n2 = 0; - } else { - pressures->he = (amb_pressure - pressures->o2) * (double)get_he(mix) / (1000 - get_o2(mix)); - pressures->n2 = amb_pressure - pressures->o2 - pressures->he; - } - } - } else { - if (divemode == PSCR) { /* The steady state approximation should be good enough */ - pressures->o2 = get_o2(mix) / 1000.0 * amb_pressure - (1.0 - get_o2(mix) / 1000.0) * prefs.o2consumption / (prefs.bottomsac * prefs.pscr_ratio / 1000.0); - if (pressures->o2 < 0) // He's dead, Jim. - pressures->o2 = 0; - if (get_o2(mix) != 1000) { - pressures->he = (amb_pressure - pressures->o2) * get_he(mix) / (1000.0 - get_o2(mix)); - pressures->n2 = (amb_pressure - pressures->o2) * (1000 - get_o2(mix) - get_he(mix)) / (1000.0 - get_o2(mix)); - } else { - pressures->he = pressures->n2 = 0; - } - } else { - // Open circuit dives: no gas pressure values available, they need to be calculated - pressures->o2 = get_o2(mix) / 1000.0 * amb_pressure; // These calculations are also used if the CCR calculation above.. - pressures->he = get_he(mix) / 1000.0 * amb_pressure; // ..returned a po2 of zero (i.e. o2 sensor data not resolvable) - pressures->n2 = (1000 - get_o2(mix) - get_he(mix)) / 1000.0 * amb_pressure; - } - } -} - -static int find_cylinder_match(cylinder_t *cyl, cylinder_t array[], unsigned int used) -{ - int i; - int best = -1, score = INT_MAX; - - if (cylinder_nodata(cyl)) - return -1; - for (i = 0; i < MAX_CYLINDERS; i++) { - const cylinder_t *match; - int distance; - - if (used & (1 << i)) - continue; - match = array + i; - distance = gasmix_distance(&cyl->gasmix, &match->gasmix); - if (distance >= score) - continue; - best = i; - score = distance; - } - return best; -} - -/* Force an initial gaschange event to the (old) gas #0 */ -static void add_initial_gaschange(struct dive *dive, struct divecomputer *dc) -{ - struct event *ev = get_next_event(dc->events, "gaschange"); - - if (ev && ev->time.seconds < 30) - return; - - /* Old starting gas mix */ - add_gas_switch_event(dive, dc, 0, 0); -} - -void dc_cylinder_renumber(struct dive *dive, struct divecomputer *dc, int mapping[]) -{ - int i; - struct event *ev; - - /* Did the first gas get remapped? Add gas switch event */ - if (mapping[0] > 0) - add_initial_gaschange(dive, dc); - - /* Remap the sensor indexes */ - for (i = 0; i < dc->samples; i++) { - struct sample *s = dc->sample + i; - int sensor; - - if (!s->cylinderpressure.mbar) - continue; - sensor = mapping[s->sensor]; - if (sensor >= 0) - s->sensor = sensor; - } - - /* Remap the gas change indexes */ - for (ev = dc->events; ev; ev = ev->next) { - if (!event_is_gaschange(ev)) - continue; - if (ev->gas.index < 0) - continue; - ev->gas.index = mapping[ev->gas.index]; - } -} - -/* - * If the cylinder indexes change (due to merging dives or deleting - * cylinders in the middle), we need to change the indexes in the - * dive computer data for this dive. - * - * Also note that we assume that the initial cylinder is cylinder 0, - * so if that got renamed, we need to create a fake gas change event - */ -static void cylinder_renumber(struct dive *dive, int mapping[]) -{ - struct divecomputer *dc; - for_each_dc (dive, dc) - dc_cylinder_renumber(dive, dc, mapping); -} - -/* - * Merging cylinder information is non-trivial, because the two dive computers - * may have different ideas of what the different cylinder indexing is. - * - * Logic: take all the cylinder information from the preferred dive ('a'), and - * then try to match each of the cylinders in the other dive by the gasmix that - * is the best match and hasn't been used yet. - */ -static void merge_cylinders(struct dive *res, struct dive *a, struct dive *b) -{ - int i, renumber = 0; - int mapping[MAX_CYLINDERS]; - unsigned int used = 0; - - /* Copy the cylinder info raw from 'a' */ - memcpy(res->cylinder, a->cylinder, sizeof(res->cylinder)); - memset(a->cylinder, 0, sizeof(a->cylinder)); - - for (i = 0; i < MAX_CYLINDERS; i++) { - int j; - cylinder_t *cyl = b->cylinder + i; - - j = find_cylinder_match(cyl, res->cylinder, used); - mapping[i] = j; - if (j < 0) - continue; - used |= 1 << j; - merge_cylinder_info(cyl, res->cylinder + j); - - /* If that renumbered the cylinders, fix it up! */ - if (i != j) - renumber = 1; - } - if (renumber) - cylinder_renumber(b, mapping); -} - -static void merge_equipment(struct dive *res, struct dive *a, struct dive *b) -{ - int i; - - merge_cylinders(res, a, b); - for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) - merge_weightsystem_info(res->weightsystem + i, a->weightsystem + i, b->weightsystem + i); -} - -static void merge_airtemps(struct dive *res, struct dive *a, struct dive *b) -{ - un_fixup_airtemp(a); - un_fixup_airtemp(b); - MERGE_NONZERO(res, a, b, airtemp.mkelvin); -} - -/* - * When merging two dives, this picks the trip from one, and removes it - * from the other. - * - * The 'next' dive is not involved in the dive merging, but is the dive - * that will be the next dive after the merged dive. - */ -static void pick_trip(struct dive *res, struct dive *pick) -{ - tripflag_t tripflag = pick->tripflag; - dive_trip_t *trip = pick->divetrip; - - res->tripflag = tripflag; - add_dive_to_trip(res, trip); -} - -/* - * Pick a trip for a dive - */ -static void merge_trip(struct dive *res, struct dive *a, struct dive *b) -{ - dive_trip_t *atrip, *btrip; - - /* - * The larger tripflag is more relevant: we prefer - * take manually assigned trips over auto-generated - * ones. - */ - if (a->tripflag > b->tripflag) - goto pick_a; - - if (a->tripflag < b->tripflag) - goto pick_b; - - /* Otherwise, look at the trip data and pick the "better" one */ - atrip = a->divetrip; - btrip = b->divetrip; - if (!atrip) - goto pick_b; - if (!btrip) - goto pick_a; - if (!atrip->location) - goto pick_b; - if (!btrip->location) - goto pick_a; - if (!atrip->notes) - goto pick_b; - if (!btrip->notes) - goto pick_a; - - /* - * Ok, so both have location and notes. - * Pick the earlier one. - */ - if (a->when < b->when) - goto pick_a; - goto pick_b; - -pick_a: - b = a; -pick_b: - pick_trip(res, b); -} - -#if CURRENTLY_NOT_USED -/* - * Sample 's' is between samples 'a' and 'b'. It is 'offset' seconds before 'b'. - * - * If 's' and 'a' are at the same time, offset is 0, and b is NULL. - */ -static int compare_sample(struct sample *s, struct sample *a, struct sample *b, int offset) -{ - unsigned int depth = a->depth.mm; - int diff; - - if (offset) { - unsigned int interval = b->time.seconds - a->time.seconds; - unsigned int depth_a = a->depth.mm; - unsigned int depth_b = b->depth.mm; - - if (offset > interval) - return -1; - - /* pick the average depth, scaled by the offset from 'b' */ - depth = (depth_a * offset) + (depth_b * (interval - offset)); - depth /= interval; - } - diff = s->depth.mm - depth; - if (diff < 0) - diff = -diff; - /* cut off at one meter difference */ - if (diff > 1000) - diff = 1000; - return diff * diff; -} - -/* - * Calculate a "difference" in samples between the two dives, given - * the offset in seconds between them. Use this to find the best - * match of samples between two different dive computers. - */ -static unsigned long sample_difference(struct divecomputer *a, struct divecomputer *b, int offset) -{ - int asamples = a->samples; - int bsamples = b->samples; - struct sample *as = a->sample; - struct sample *bs = b->sample; - unsigned long error = 0; - int start = -1; - - if (!asamples || !bsamples) - return 0; - - /* - * skip the first sample - this way we know can always look at - * as/bs[-1] to look at the samples around it in the loop. - */ - as++; - bs++; - asamples--; - bsamples--; - - for (;;) { - int at, bt, diff; - - - /* If we run out of samples, punt */ - if (!asamples) - return INT_MAX; - if (!bsamples) - return INT_MAX; - - at = as->time.seconds; - bt = bs->time.seconds + offset; - - /* b hasn't started yet? Ignore it */ - if (bt < 0) { - bs++; - bsamples--; - continue; - } - - if (at < bt) { - diff = compare_sample(as, bs - 1, bs, bt - at); - as++; - asamples--; - } else if (at > bt) { - diff = compare_sample(bs, as - 1, as, at - bt); - bs++; - bsamples--; - } else { - diff = compare_sample(as, bs, NULL, 0); - as++; - bs++; - asamples--; - bsamples--; - } - - /* Invalid comparison point? */ - if (diff < 0) - continue; - - if (start < 0) - start = at; - - error += diff; - - if (at - start > 120) - break; - } - return error; -} - -/* - * Dive 'a' is 'offset' seconds before dive 'b' - * - * This is *not* because the dive computers clocks aren't in sync, - * it is because the dive computers may "start" the dive at different - * points in the dive, so the sample at time X in dive 'a' is the - * same as the sample at time X+offset in dive 'b'. - * - * For example, some dive computers take longer to "wake up" when - * they sense that you are under water (ie Uemis Zurich if it was off - * when the dive started). And other dive computers have different - * depths that they activate at, etc etc. - * - * If we cannot find a shared offset, don't try to merge. - */ -static int find_sample_offset(struct divecomputer *a, struct divecomputer *b) -{ - int offset, best; - unsigned long max; - - /* No samples? Merge at any time (0 offset) */ - if (!a->samples) - return 0; - if (!b->samples) - return 0; - - /* - * Common special-case: merging a dive that came from - * the same dive computer, so the samples are identical. - * Check this first, without wasting time trying to find - * some minimal offset case. - */ - best = 0; - max = sample_difference(a, b, 0); - if (!max) - return 0; - - /* - * Otherwise, look if we can find anything better within - * a thirty second window.. - */ - for (offset = -30; offset <= 30; offset++) { - unsigned long diff; - - diff = sample_difference(a, b, offset); - if (diff > max) - continue; - best = offset; - max = diff; - } - - return best; -} -#endif - -/* - * Are a and b "similar" values, when given a reasonable lower end expected - * difference? - * - * So for example, we'd expect different dive computers to give different - * max depth readings. You might have them on different arms, and they - * have different pressure sensors and possibly different ideas about - * water salinity etc. - * - * So have an expected minimum difference, but also allow a larger relative - * error value. - */ -static int similar(unsigned long a, unsigned long b, unsigned long expected) -{ - if (!a && !b) - return 1; - - if (a && b) { - unsigned long min, max, diff; - - min = a; - max = b; - if (a > b) { - min = b; - max = a; - } - diff = max - min; - - /* Smaller than expected difference? */ - if (diff < expected) - return 1; - /* Error less than 10% or the maximum */ - if (diff * 10 < max) - return 1; - } - return 0; -} - -/* - * Match two dive computer entries against each other, and - * tell if it's the same dive. Return 0 if "don't know", - * positive for "same dive" and negative for "definitely - * not the same dive" - */ -int match_one_dc(struct divecomputer *a, struct divecomputer *b) -{ - /* Not same model? Don't know if matching.. */ - if (!a->model || !b->model) - return 0; - if (strcasecmp(a->model, b->model)) - return 0; - - /* Different device ID's? Don't know */ - if (a->deviceid != b->deviceid) - return 0; - - /* Do we have dive IDs? */ - if (!a->diveid || !b->diveid) - return 0; - - /* - * If they have different dive ID's on the same - * dive computer, that's a definite "same or not" - */ - return a->diveid == b->diveid ? 1 : -1; -} - -/* - * Match every dive computer against each other to see if - * we have a matching dive. - * - * Return values: - * -1 for "is definitely *NOT* the same dive" - * 0 for "don't know" - * 1 for "is definitely the same dive" - */ -static int match_dc_dive(struct divecomputer *a, struct divecomputer *b) -{ - do { - struct divecomputer *tmp = b; - do { - int match = match_one_dc(a, tmp); - if (match) - return match; - tmp = tmp->next; - } while (tmp); - a = a->next; - } while (a); - return 0; -} - -static bool new_without_trip(struct dive *a) -{ - return a->downloaded && !a->divetrip; -} - -/* - * Do we want to automatically try to merge two dives that - * look like they are the same dive? - * - * This happens quite commonly because you download a dive - * that you already had, or perhaps because you maintained - * multiple dive logs and want to load them all together - * (possibly one of them was imported from another dive log - * application entirely). - * - * NOTE! We mainly look at the dive time, but it can differ - * between two dives due to a few issues: - * - * - rounding the dive date to the nearest minute in other dive - * applications - * - * - dive computers with "relative datestamps" (ie the dive - * computer doesn't actually record an absolute date at all, - * but instead at download-time synchronizes its internal - * time with real-time on the downloading computer) - * - * - using multiple dive computers with different real time on - * the same dive - * - * We do not merge dives that look radically different, and if - * the dates are *too* far off the user will have to join two - * dives together manually. But this tries to handle the sane - * cases. - */ -static int likely_same_dive(struct dive *a, struct dive *b) -{ - int match, fuzz = 20 * 60; - - /* don't merge manually added dives with anything */ - if (same_string(a->dc.model, "manually added dive") || - same_string(b->dc.model, "manually added dive")) - return 0; - - /* Don't try to merge dives with different trip information */ - if (a->divetrip != b->divetrip) { - /* - * Exception: if the dive is downloaded without any - * explicit trip information, we do want to merge it - * with existing old dives even if they have trips. - */ - if (!new_without_trip(a) && !new_without_trip(b)) - return 0; - } - - /* - * Do some basic sanity testing of the values we - * have filled in during 'fixup_dive()' - */ - if (!similar(a->maxdepth.mm, b->maxdepth.mm, 1000) || - (a->meandepth.mm && b->meandepth.mm && !similar(a->meandepth.mm, b->meandepth.mm, 1000)) || - !similar(a->duration.seconds, b->duration.seconds, 5 * 60)) - return 0; - - /* See if we can get an exact match on the dive computer */ - match = match_dc_dive(&a->dc, &b->dc); - if (match) - return match > 0; - - /* - * Allow a time difference due to dive computer time - * setting etc. Check if they overlap. - */ - fuzz = MAX(a->duration.seconds, b->duration.seconds) / 2; - if (fuzz < 60) - fuzz = 60; - - return ((a->when <= b->when + fuzz) && (a->when >= b->when - fuzz)); -} - -/* - * This could do a lot more merging. Right now it really only - * merges almost exact duplicates - something that happens easily - * with overlapping dive downloads. - */ -struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded) -{ - if (likely_same_dive(a, b)) - return merge_dives(a, b, 0, prefer_downloaded); - return NULL; -} - -void free_events(struct event *ev) -{ - while (ev) { - struct event *next = ev->next; - free(ev); - ev = next; - } -} - -static void free_dc(struct divecomputer *dc) -{ - free(dc->sample); - free((void *)dc->model); - free_events(dc->events); - free(dc); -} - -static void free_pic(struct picture *picture) -{ - if (picture) { - free(picture->filename); - free(picture); - } -} - -static int same_sample(struct sample *a, struct sample *b) -{ - if (a->time.seconds != b->time.seconds) - return 0; - if (a->depth.mm != b->depth.mm) - return 0; - if (a->temperature.mkelvin != b->temperature.mkelvin) - return 0; - if (a->cylinderpressure.mbar != b->cylinderpressure.mbar) - return 0; - return a->sensor == b->sensor; -} - -static int same_dc(struct divecomputer *a, struct divecomputer *b) -{ - int i; - struct event *eva, *evb; - - i = match_one_dc(a, b); - if (i) - return i > 0; - - if (a->when && b->when && a->when != b->when) - return 0; - if (a->samples != b->samples) - return 0; - for (i = 0; i < a->samples; i++) - if (!same_sample(a->sample + i, b->sample + i)) - return 0; - eva = a->events; - evb = b->events; - while (eva && evb) { - if (!same_event(eva, evb)) - return 0; - eva = eva->next; - evb = evb->next; - } - return eva == evb; -} - -static int might_be_same_device(struct divecomputer *a, struct divecomputer *b) -{ - /* No dive computer model? That matches anything */ - if (!a->model || !b->model) - return 1; - - /* Otherwise at least the model names have to match */ - if (strcasecmp(a->model, b->model)) - return 0; - - /* No device ID? Match */ - if (!a->deviceid || !b->deviceid) - return 1; - - return a->deviceid == b->deviceid; -} - -static void remove_redundant_dc(struct divecomputer *dc, int prefer_downloaded) -{ - do { - struct divecomputer **p = &dc->next; - - /* Check this dc against all the following ones.. */ - while (*p) { - struct divecomputer *check = *p; - if (same_dc(dc, check) || (prefer_downloaded && might_be_same_device(dc, check))) { - *p = check->next; - check->next = NULL; - free_dc(check); - continue; - } - p = &check->next; - } - - /* .. and then continue down the chain, but we */ - prefer_downloaded = 0; - dc = dc->next; - } while (dc); -} - -static void clear_dc(struct divecomputer *dc) -{ - memset(dc, 0, sizeof(*dc)); -} - -static struct divecomputer *find_matching_computer(struct divecomputer *match, struct divecomputer *list) -{ - struct divecomputer *p; - - while ((p = list) != NULL) { - list = list->next; - - if (might_be_same_device(match, p)) - break; - } - return p; -} - - -static void copy_dive_computer(struct divecomputer *res, struct divecomputer *a) -{ - *res = *a; - res->model = copy_string(a->model); - res->samples = res->alloc_samples = 0; - res->sample = NULL; - res->events = NULL; - res->next = NULL; -} - -/* - * Join dive computers with a specific time offset between - * them. - * - * Use the dive computer ID's (or names, if ID's are missing) - * to match them up. If we find a matching dive computer, we - * merge them. If not, we just take the data from 'a'. - */ -static void interleave_dive_computers(struct divecomputer *res, - struct divecomputer *a, struct divecomputer *b, int offset) -{ - do { - struct divecomputer *match; - - copy_dive_computer(res, a); - - match = find_matching_computer(a, b); - if (match) { - merge_events(res, a, match, offset); - merge_samples(res, a, match, offset); - /* Use the diveid of the later dive! */ - if (offset > 0) - res->diveid = match->diveid; - } else { - res->sample = a->sample; - res->samples = a->samples; - res->events = a->events; - a->sample = NULL; - a->samples = 0; - a->events = NULL; - } - a = a->next; - if (!a) - break; - res->next = calloc(1, sizeof(struct divecomputer)); - res = res->next; - } while (res); -} - - -/* - * Join dive computer information. - * - * If we have old-style dive computer information (no model - * name etc), we will prefer a new-style one and just throw - * away the old. We're assuming it's a re-download. - * - * Otherwise, we'll just try to keep all the information, - * unless the user has specified that they prefer the - * downloaded computer, in which case we'll aggressively - * try to throw out old information that *might* be from - * that one. - */ -static void join_dive_computers(struct divecomputer *res, struct divecomputer *a, struct divecomputer *b, int prefer_downloaded) -{ - struct divecomputer *tmp; - - if (a->model && !b->model) { - *res = *a; - clear_dc(a); - return; - } - if (b->model && !a->model) { - *res = *b; - clear_dc(b); - return; - } - - *res = *a; - clear_dc(a); - tmp = res; - while (tmp->next) - tmp = tmp->next; - - tmp->next = calloc(1, sizeof(*tmp)); - *tmp->next = *b; - clear_dc(b); - - remove_redundant_dc(res, prefer_downloaded); -} - -static bool tag_seen_before(struct tag_entry *start, struct tag_entry *before) -{ - while (start && start != before) { - if (same_string(start->tag->name, before->tag->name)) - return true; - start = start->next; - } - return false; -} - -/* remove duplicates and empty nodes */ -void taglist_cleanup(struct tag_entry **tag_list) -{ - struct tag_entry **tl = tag_list; - while (*tl) { - /* skip tags that are empty or that we have seen before */ - if (same_string((*tl)->tag->name, "") || tag_seen_before(*tag_list, *tl)) { - *tl = (*tl)->next; - continue; - } - tl = &(*tl)->next; - } -} - -int taglist_get_tagstring(struct tag_entry *tag_list, char *buffer, int len) -{ - int i = 0; - struct tag_entry *tmp; - tmp = tag_list; - memset(buffer, 0, len); - while (tmp != NULL) { - int newlength = strlen(tmp->tag->name); - if (i > 0) - newlength += 2; - if ((i + newlength) < len) { - if (i > 0) { - strcpy(buffer + i, ", "); - strcpy(buffer + i + 2, tmp->tag->name); - } else { - strcpy(buffer, tmp->tag->name); - } - } else { - return i; - } - i += newlength; - tmp = tmp->next; - } - return i; -} - -static inline void taglist_free_divetag(struct divetag *tag) -{ - if (tag->name != NULL) - free(tag->name); - if (tag->source != NULL) - free(tag->source); - free(tag); -} - -/* Add a tag to the tag_list, keep the list sorted */ -static struct divetag *taglist_add_divetag(struct tag_entry **tag_list, struct divetag *tag) -{ - struct tag_entry *next, *entry; - - while ((next = *tag_list) != NULL) { - int cmp = strcmp(next->tag->name, tag->name); - - /* Already have it? */ - if (!cmp) - return next->tag; - /* Is the entry larger? If so, insert here */ - if (cmp > 0) - break; - /* Continue traversing the list */ - tag_list = &next->next; - } - - /* Insert in front of it */ - entry = malloc(sizeof(struct tag_entry)); - entry->next = next; - entry->tag = tag; - *tag_list = entry; - return tag; -} - -struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag) -{ - size_t i = 0; - int is_default_tag = 0; - struct divetag *ret_tag, *new_tag; - const char *translation; - new_tag = malloc(sizeof(struct divetag)); - - for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++) { - if (strcmp(default_tags[i], tag) == 0) { - is_default_tag = 1; - break; - } - } - /* Only translate default tags */ - if (is_default_tag) { - translation = translate("gettextFromC", tag); - new_tag->name = malloc(strlen(translation) + 1); - memcpy(new_tag->name, translation, strlen(translation) + 1); - new_tag->source = malloc(strlen(tag) + 1); - memcpy(new_tag->source, tag, strlen(tag) + 1); - } else { - new_tag->source = NULL; - new_tag->name = malloc(strlen(tag) + 1); - memcpy(new_tag->name, tag, strlen(tag) + 1); - } - /* Try to insert new_tag into g_tag_list if we are not operating on it */ - if (tag_list != &g_tag_list) { - ret_tag = taglist_add_divetag(&g_tag_list, new_tag); - /* g_tag_list already contains new_tag, free the duplicate */ - if (ret_tag != new_tag) - taglist_free_divetag(new_tag); - ret_tag = taglist_add_divetag(tag_list, ret_tag); - } else { - ret_tag = taglist_add_divetag(tag_list, new_tag); - if (ret_tag != new_tag) - taglist_free_divetag(new_tag); - } - return ret_tag; -} - -void taglist_free(struct tag_entry *entry) -{ - STRUCTURED_LIST_FREE(struct tag_entry, entry, free) -} - -/* Merge src1 and src2, write to *dst */ -static void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2) -{ - struct tag_entry *entry; - - for (entry = src1; entry; entry = entry->next) - taglist_add_divetag(dst, entry->tag); - for (entry = src2; entry; entry = entry->next) - taglist_add_divetag(dst, entry->tag); -} - -void taglist_init_global() -{ - size_t i; - - for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++) - taglist_add_tag(&g_tag_list, default_tags[i]); -} - -bool taglist_contains(struct tag_entry *tag_list, const char *tag) -{ - while (tag_list) { - if (same_string(tag_list->tag->name, tag)) - return true; - tag_list = tag_list->next; - } - return false; -} - -// check if all tags in subtl are included in supertl (so subtl is a subset of supertl) -static bool taglist_contains_all(struct tag_entry *subtl, struct tag_entry *supertl) -{ - while (subtl) { - if (!taglist_contains(supertl, subtl->tag->name)) - return false; - subtl = subtl->next; - } - return true; -} - -struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list) -{ - struct tag_entry *added_list = NULL; - while (new_list) { - if (!taglist_contains(original_list, new_list->tag->name)) - taglist_add_tag(&added_list, new_list->tag->name); - new_list = new_list->next; - } - return added_list; -} - -void dump_taglist(const char *intro, struct tag_entry *tl) -{ - char *comma = ""; - fprintf(stderr, "%s", intro); - while(tl) { - fprintf(stderr, "%s %s", comma, tl->tag->name); - comma = ","; - tl = tl->next; - } - fprintf(stderr, "\n"); -} - -// if tl1 is both a subset and superset of tl2 they must be the same -bool taglist_equal(struct tag_entry *tl1, struct tag_entry *tl2) -{ - return taglist_contains_all(tl1, tl2) && taglist_contains_all(tl2, tl1); -} - -// count the dives where the tag list contains the given tag -int count_dives_with_tag(const char *tag) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (same_string(tag, "")) { - // count dives with no tags - if (d->tag_list == NULL) - counter++; - } else if (taglist_contains(d->tag_list, tag)) { - counter++; - } - } - return counter; -} - -extern bool string_sequence_contains(const char *string_sequence, const char *text); - -// count the dives where the person is included in the comma separated string sequences of buddies or divemasters -int count_dives_with_person(const char *person) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (same_string(person, "")) { - // solo dive - if (same_string(d->buddy, "") && same_string(d->divemaster, "")) - counter++; - } else if (string_sequence_contains(d->buddy, person) || string_sequence_contains(d->divemaster, person)) { - counter++; - } - } - return counter; -} - -// count the dives with exactly the location -int count_dives_with_location(const char *location) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (same_string(get_dive_location(d), location)) - counter++; - } - return counter; -} - -// count the dives with exactly the suit -int count_dives_with_suit(const char *suit) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (same_string(d->suit, suit)) - counter++; - } - return counter; -} - -/* - * Merging two dives can be subtle, because there's two different ways - * of merging: - * - * (a) two distinctly _different_ dives that have the same dive computer - * are merged into one longer dive, because the user asked for it - * in the divelist. - * - * Because this case is with the same dive computer, we *know* the - * two must have a different start time, and "offset" is the relative - * time difference between the two. - * - * (a) two different dive computers that we might want to merge into - * one single dive with multiple dive computers. - * - * This is the "try_to_merge()" case, which will have offset == 0, - * even if the dive times might be different. - */ -struct dive *merge_dives(struct dive *a, struct dive *b, int offset, bool prefer_downloaded) -{ - struct dive *res = alloc_dive(); - struct dive *dl = NULL; - - if (offset) { - /* - * If "likely_same_dive()" returns true, that means that - * it is *not* the same dive computer, and we do not want - * to try to turn it into a single longer dive. So we'd - * join them as two separate dive computers at zero offset. - */ - if (likely_same_dive(a, b)) - offset = 0; - } else { - /* Aim for newly downloaded dives to be 'b' (keep old dive data first) */ - if (a->downloaded && !b->downloaded) { - struct dive *tmp = a; - a = b; - b = tmp; - } - if (prefer_downloaded && b->downloaded) - dl = b; - } - - res->when = dl ? dl->when : a->when; - res->selected = a->selected || b->selected; - merge_trip(res, a, b); - MERGE_TXT(res, a, b, notes); - MERGE_TXT(res, a, b, buddy); - MERGE_TXT(res, a, b, divemaster); - MERGE_MAX(res, a, b, rating); - MERGE_TXT(res, a, b, suit); - MERGE_MAX(res, a, b, number); - MERGE_NONZERO(res, a, b, cns); - MERGE_NONZERO(res, a, b, visibility); - MERGE_NONZERO(res, a, b, picture_list); - taglist_merge(&res->tag_list, a->tag_list, b->tag_list); - merge_equipment(res, a, b); - merge_airtemps(res, a, b); - if (dl) { - /* If we prefer downloaded, do those first, and get rid of "might be same" computers */ - join_dive_computers(&res->dc, &dl->dc, &a->dc, 1); - } else if (offset && might_be_same_device(&a->dc, &b->dc)) - interleave_dive_computers(&res->dc, &a->dc, &b->dc, offset); - else - join_dive_computers(&res->dc, &a->dc, &b->dc, 0); - res->dive_site_uuid = a->dive_site_uuid ?: b->dive_site_uuid; - fixup_dive(res); - return res; -} - -// copy_dive(), but retaining the new ID for the copied dive -static struct dive *create_new_copy(struct dive *from) -{ - struct dive *to = alloc_dive(); - int id; - - // alloc_dive() gave us a new ID, we just need to - // make sure it's not overwritten. - id = to->id; - copy_dive(from, to); - to->id = id; - return to; -} - -static void force_fixup_dive(struct dive *d) -{ - struct divecomputer *dc = &d->dc; - int old_temp = dc->watertemp.mkelvin; - int old_mintemp = d->mintemp.mkelvin; - int old_maxtemp = d->maxtemp.mkelvin; - duration_t old_duration = d->duration; - - d->maxdepth.mm = 0; - dc->maxdepth.mm = 0; - d->watertemp.mkelvin = 0; - dc->watertemp.mkelvin = 0; - d->duration.seconds = 0; - d->maxtemp.mkelvin = 0; - d->mintemp.mkelvin = 0; - - fixup_dive(d); - - if (!d->watertemp.mkelvin) - d->watertemp.mkelvin = old_temp; - - if (!dc->watertemp.mkelvin) - dc->watertemp.mkelvin = old_temp; - - if (!d->maxtemp.mkelvin) - d->maxtemp.mkelvin = old_maxtemp; - - if (!d->mintemp.mkelvin) - d->mintemp.mkelvin = old_mintemp; - - if (!d->duration.seconds) - d->duration = old_duration; - -} - -/* - * Split a dive that has a surface interval from samples 'a' to 'b' - * into two dives. - */ -static int split_dive_at(struct dive *dive, int a, int b) -{ - int i, nr; - uint32_t t; - struct dive *d1, *d2; - struct divecomputer *dc1, *dc2; - struct event *event, **evp; - - /* if we can't find the dive in the dive list, don't bother */ - if ((nr = get_divenr(dive)) < 0) - return 0; - - /* We're not trying to be efficient here.. */ - d1 = create_new_copy(dive); - d2 = create_new_copy(dive); - - /* now unselect the first first segment so we don't keep all - * dives selected by mistake. But do keep the second one selected - * so the algorithm keeps splitting the dive further */ - d1->selected = false; - - dc1 = &d1->dc; - dc2 = &d2->dc; - /* - * Cut off the samples of d1 at the beginning - * of the interval. - */ - dc1->samples = a; - - /* And get rid of the 'b' first samples of d2 */ - dc2->samples -= b; - memmove(dc2->sample, dc2->sample+b, dc2->samples * sizeof(struct sample)); - - /* - * This is where we cut off events from d1, - * and shift everything in d2 - */ - t = dc2->sample[0].time.seconds; - d2->when += t; - for (i = 0; i < dc2->samples; i++) - dc2->sample[i].time.seconds -= t; - - /* Remove the events past 't' from d1 */ - evp = &dc1->events; - while ((event = *evp) != NULL && event->time.seconds < t) - evp = &event->next; - *evp = NULL; - while (event) { - struct event *next = event->next; - free(event); - event = next; - } - - /* Remove the events before 't' from d2, and shift the rest */ - evp = &dc2->events; - while ((event = *evp) != NULL) { - if (event->time.seconds < t) { - *evp = event->next; - free(event); - } else { - event->time.seconds -= t; - } - } - - force_fixup_dive(d1); - force_fixup_dive(d2); - - if (dive->divetrip) { - d1->divetrip = d2->divetrip = 0; - add_dive_to_trip(d1, dive->divetrip); - add_dive_to_trip(d2, dive->divetrip); - } - - delete_single_dive(nr); - add_single_dive(nr, d1); - - /* - * Was the dive numbered? If it was the last dive, then we'll - * increment the dive number for the tail part that we split off. - * Otherwise the tail is unnumbered. - */ - if (d2->number) { - if (dive_table.nr == nr + 1) - d2->number++; - else - d2->number = 0; - } - add_single_dive(nr + 1, d2); - - mark_divelist_changed(true); - - return 1; -} - -/* in freedive mode we split for as little as 10 seconds on the surface, - * otherwise we use a minute */ -static bool should_split(struct divecomputer *dc, int t1, int t2) -{ - int threshold = dc->divemode == FREEDIVE ? 10 : 60; - - return t2 - t1 >= threshold; -} - -/* - * Try to split a dive into multiple dives at a surface interval point. - * - * NOTE! We will not split dives with multiple dive computers, and - * only split when there is at least one surface event that has - * non-surface events on both sides. - * - * In other words, this is a (simplified) reversal of the dive merging. - */ -int split_dive(struct dive *dive) -{ - int i; - int at_surface, surface_start; - struct divecomputer *dc; - - if (!dive || (dc = &dive->dc)->next) - return 0; - - surface_start = 0; - at_surface = 1; - for (i = 1; i < dc->samples; i++) { - struct sample *sample = dc->sample+i; - int surface_sample = sample->depth.mm < SURFACE_THRESHOLD; - - /* - * We care about the transition from and to depth 0, - * not about the depth staying similar. - */ - if (at_surface == surface_sample) - continue; - at_surface = surface_sample; - - // Did it become surface after having been non-surface? We found the start - if (at_surface) { - surface_start = i; - continue; - } - - // Going down again? We want at least a minute from - // the surface start. - if (!surface_start) - continue; - if (!should_split(dc, dc->sample[surface_start].time.seconds, sample[i - 1].time.seconds)) - continue; - - return split_dive_at(dive, surface_start, i-1); - } - return 0; -} - -/* - * "dc_maxtime()" is how much total time this dive computer - * has for this dive. Note that it can differ from "duration" - * if there are surface events in the middle. - * - * Still, we do ignore all but the last surface samples from the - * end, because some divecomputers just generate lots of them. - */ -static inline int dc_totaltime(const struct divecomputer *dc) -{ - int time = dc->duration.seconds; - int nr = dc->samples; - - while (nr--) { - struct sample *s = dc->sample + nr; - time = s->time.seconds; - if (s->depth.mm >= SURFACE_THRESHOLD) - break; - } - return time; -} - -/* - * The end of a dive is actually not trivial, because "duration" - * is not the duration until the end, but the time we spend under - * water, which can be very different if there are surface events - * during the dive. - * - * So walk the dive computers, looking for the longest actual - * time in the samples (and just default to the dive duration if - * there are no samples). - */ -static inline int dive_totaltime(const struct dive *dive) -{ - int time = dive->duration.seconds; - const struct divecomputer *dc; - - for_each_dc(dive, dc) { - int dc_time = dc_totaltime(dc); - if (dc_time > time) - time = dc_time; - } - return time; -} - -timestamp_t dive_endtime(const struct dive *dive) -{ - return dive->when + dive_totaltime(dive); -} - -struct dive *find_dive_including(timestamp_t when) -{ - int i; - struct dive *dive; - - /* binary search, anyone? Too lazy for now; - * also we always use the duration from the first divecomputer - * could this ever be a problem? */ - for_each_dive (i, dive) { - if (dive->when <= when && when <= dive_endtime(dive)) - return dive; - } - return NULL; -} - -bool time_during_dive_with_offset(struct dive *dive, timestamp_t when, timestamp_t offset) -{ - timestamp_t start = dive->when; - timestamp_t end = dive_endtime(dive); - return start - offset <= when && when <= end + offset; -} - -bool dive_within_time_range(struct dive *dive, timestamp_t when, timestamp_t offset) -{ - timestamp_t start = dive->when; - timestamp_t end = dive_endtime(dive); - return when - offset <= start && end <= when + offset; -} - -/* find the n-th dive that is part of a group of dives within the offset around 'when'. - * How is that for a vague definition of what this function should do... */ -struct dive *find_dive_n_near(timestamp_t when, int n, timestamp_t offset) -{ - int i, j = 0; - struct dive *dive; - - for_each_dive (i, dive) { - if (dive_within_time_range(dive, when, offset)) - if (++j == n) - return dive; - } - return NULL; -} - -void shift_times(const timestamp_t amount) -{ - int i; - struct dive *dive; - - for_each_dive (i, dive) { - if (!dive->selected) - continue; - dive->when += amount; - invalidate_dive_cache(dive); - } -} - -timestamp_t get_times() -{ - int i; - struct dive *dive; - - for_each_dive (i, dive) { - if (dive->selected) - break; - } - return dive->when; -} - -void set_save_userid_local(short value) -{ - prefs.save_userid_local = value; -} - -void set_userid(char *rUserId) -{ - if (prefs.userid) - free(prefs.userid); - prefs.userid = strdup(rUserId); - if (strlen(prefs.userid) > 30) - prefs.userid[30]='\0'; -} - -/* this sets a usually unused copy of the preferences with the units - * that were active the last time the dive list was saved to git storage - * (this isn't used in XML files); storing the unit preferences in the - * data file is usually pointless (that's a setting of the software, - * not a property of the data), but it's a great hint of what the user - * might expect to see when creating a backend service that visualizes - * the dive list without Subsurface running - so this is basically a - * functionality for the core library that Subsurface itself doesn't - * use but that another consumer of the library (like an HTML exporter) - * will need */ -void set_informational_units(char *units) -{ - if (strstr(units, "METRIC")) { - informational_prefs.unit_system = METRIC; - } else if (strstr(units, "IMPERIAL")) { - informational_prefs.unit_system = IMPERIAL; - } else if (strstr(units, "PERSONALIZE")) { - informational_prefs.unit_system = PERSONALIZE; - if (strstr(units, "METERS")) - informational_prefs.units.length = METERS; - if (strstr(units, "FEET")) - informational_prefs.units.length = FEET; - if (strstr(units, "LITER")) - informational_prefs.units.volume = LITER; - if (strstr(units, "CUFT")) - informational_prefs.units.volume = CUFT; - if (strstr(units, "BAR")) - informational_prefs.units.pressure = BAR; - if (strstr(units, "PSI")) - informational_prefs.units.pressure = PSI; - if (strstr(units, "PASCAL")) - informational_prefs.units.pressure = PASCAL; - if (strstr(units, "CELSIUS")) - informational_prefs.units.temperature = CELSIUS; - if (strstr(units, "FAHRENHEIT")) - informational_prefs.units.temperature = FAHRENHEIT; - if (strstr(units, "KG")) - informational_prefs.units.weight = KG; - if (strstr(units, "LBS")) - informational_prefs.units.weight = LBS; - if (strstr(units, "SECONDS")) - informational_prefs.units.vertical_speed_time = SECONDS; - if (strstr(units, "MINUTES")) - informational_prefs.units.vertical_speed_time = MINUTES; - } -} - -void average_max_depth(struct diveplan *dive, int *avg_depth, int *max_depth) -{ - int integral = 0; - int last_time = 0; - int last_depth = 0; - struct divedatapoint *dp = dive->dp; - - *max_depth = 0; - - while (dp) { - if (dp->time) { - /* Ignore gas indication samples */ - integral += (dp->depth + last_depth) * (dp->time - last_time) / 2; - last_time = dp->time; - last_depth = dp->depth; - if (dp->depth > *max_depth) - *max_depth = dp->depth; - } - dp = dp->next; - } - if (last_time) - *avg_depth = integral / last_time; - else - *avg_depth = *max_depth = 0; -} - -struct picture *alloc_picture() -{ - struct picture *pic = malloc(sizeof(struct picture)); - if (!pic) - exit(1); - memset(pic, 0, sizeof(struct picture)); - return pic; -} - -static bool new_picture_for_dive(struct dive *d, char *filename) -{ - FOR_EACH_PICTURE (d) { - if (same_string(picture->filename, filename)) - return false; - } - return true; -} - -// only add pictures that have timestamps between 30 minutes before the dive and -// 30 minutes after the dive ends -#define D30MIN (30 * 60) -bool dive_check_picture_time(struct dive *d, int shift_time, timestamp_t timestamp) -{ - offset_t offset; - if (timestamp) { - offset.seconds = timestamp - d->when + shift_time; - if (offset.seconds > -D30MIN && offset.seconds < dive_totaltime(d) + D30MIN) { - // this picture belongs to this dive - return true; - } - } - return false; -} - -bool picture_check_valid(char *filename, int shift_time) -{ - int i; - struct dive *dive; - - timestamp_t timestamp = picture_get_timestamp(filename); - for_each_dive (i, dive) - if (dive->selected && dive_check_picture_time(dive, shift_time, timestamp)) - return true; - return false; -} - -void dive_create_picture(struct dive *dive, char *filename, int shift_time, bool match_all) -{ - timestamp_t timestamp = picture_get_timestamp(filename); - if (!new_picture_for_dive(dive, filename)) - return; - if (!match_all && !dive_check_picture_time(dive, shift_time, timestamp)) - return; - - struct picture *picture = alloc_picture(); - picture->filename = strdup(filename); - picture->offset.seconds = timestamp - dive->when + shift_time; - picture_load_exif_data(picture); - - dive_add_picture(dive, picture); - dive_set_geodata_from_picture(dive, picture); - invalidate_dive_cache(dive); -} - -void dive_add_picture(struct dive *dive, struct picture *newpic) -{ - struct picture **pic_ptr = &dive->picture_list; - /* let's keep the list sorted by time */ - while (*pic_ptr && (*pic_ptr)->offset.seconds <= newpic->offset.seconds) - pic_ptr = &(*pic_ptr)->next; - newpic->next = *pic_ptr; - *pic_ptr = newpic; - cache_picture(newpic); - return; -} - -unsigned int dive_get_picture_count(struct dive *dive) -{ - unsigned int i = 0; - FOR_EACH_PICTURE (dive) - i++; - return i; -} - -void dive_set_geodata_from_picture(struct dive *dive, struct picture *picture) -{ - struct dive_site *ds = get_dive_site_by_uuid(dive->dive_site_uuid); - if (!dive_site_has_gps_location(ds) && (picture->latitude.udeg || picture->longitude.udeg)) { - if (ds) { - ds->latitude = picture->latitude; - ds->longitude = picture->longitude; - } else { - dive->dive_site_uuid = create_dive_site_with_gps("", picture->latitude, picture->longitude, dive->when); - invalidate_dive_cache(dive); - } - } -} - -void picture_free(struct picture *picture) -{ - if (!picture) - return; - free(picture->filename); - free(picture->hash); - free(picture); -} - -// When handling pictures in different threads, we need to copy them so we don't -// run into problems when the main thread frees the picture. - -struct picture *clone_picture(struct picture *src) -{ - struct picture *dst; - - dst = alloc_picture(); - copy_pl(src, dst); - return dst; -} - -void dive_remove_picture(char *filename) -{ - struct picture **picture = ¤t_dive->picture_list; - while (picture && !same_string((*picture)->filename, filename)) - picture = &(*picture)->next; - if (picture) { - struct picture *temp = (*picture)->next; - picture_free(*picture); - *picture = temp; - invalidate_dive_cache(current_dive); - } -} - -/* this always acts on the current divecomputer of the current dive */ -void make_first_dc() -{ - struct divecomputer *dc = ¤t_dive->dc; - struct divecomputer *newdc = malloc(sizeof(*newdc)); - struct divecomputer *cur_dc = current_dc; /* needs to be in a local variable so the macro isn't re-executed */ - - /* skip the current DC in the linked list */ - while (dc && dc->next != cur_dc) - dc = dc->next; - if (!dc) { - free(newdc); - fprintf(stderr, "data inconsistent: can't find the current DC"); - return; - } - dc->next = cur_dc->next; - *newdc = current_dive->dc; - current_dive->dc = *cur_dc; - current_dive->dc.next = newdc; - free(cur_dc); - invalidate_dive_cache(current_dive); -} - -/* always acts on the current dive */ -unsigned int count_divecomputers(void) -{ - int ret = 1; - struct divecomputer *dc = current_dive->dc.next; - while (dc) { - ret++; - dc = dc->next; - } - return ret; -} - -/* always acts on the current dive */ -void delete_current_divecomputer(void) -{ - struct divecomputer *dc = current_dc; - - if (dc == ¤t_dive->dc) { - /* remove the first one, so copy the second one in place of the first and free the second one - * be careful about freeing the no longer needed structures - since we copy things around we can't use free_dc()*/ - struct divecomputer *fdc = dc->next; - free(dc->sample); - free((void *)dc->model); - free_events(dc->events); - memcpy(dc, fdc, sizeof(struct divecomputer)); - free(fdc); - } else { - struct divecomputer *pdc = ¤t_dive->dc; - while (pdc->next != dc && pdc->next) - pdc = pdc->next; - if (pdc->next == dc) { - pdc->next = dc->next; - free_dc(dc); - } - } - if (dc_number == count_divecomputers()) - dc_number--; - invalidate_dive_cache(current_dive); -} - -/* helper function to make it easier to work with our structures - * we don't interpolate here, just use the value from the last sample up to that time */ -int get_depth_at_time(struct divecomputer *dc, unsigned int time) -{ - int depth = 0; - if (dc && dc->sample) - for (int i = 0; i < dc->samples; i++) { - if (dc->sample[i].time.seconds > time) - break; - depth = dc->sample[i].depth.mm; - } - return depth; -} diff --git a/subsurface-core/dive.h b/subsurface-core/dive.h deleted file mode 100644 index ae24b7409..000000000 --- a/subsurface-core/dive.h +++ /dev/null @@ -1,913 +0,0 @@ -#ifndef DIVE_H -#define DIVE_H - -#include <stdlib.h> -#include <stdint.h> -#include <time.h> -#include <math.h> -#include <zip.h> -#include <sqlite3.h> -#include <string.h> -#include "divesite.h" - -/* Windows has no MIN/MAX macros - so let's just roll our own */ -#define MIN(x, y) ({ \ - __typeof__(x) _min1 = (x); \ - __typeof__(y) _min2 = (y); \ - (void) (&_min1 == &_min2); \ - _min1 < _min2 ? _min1 : _min2; }) - -#define MAX(x, y) ({ \ - __typeof__(x) _max1 = (x); \ - __typeof__(y) _max2 = (y); \ - (void) (&_max1 == &_max2); \ - _max1 > _max2 ? _max1 : _max2; }) - -#define IS_FP_SAME(_a, _b) (fabs((_a) - (_b)) <= 0.000001 * MAX(fabs(_a), fabs(_b))) - -static inline int same_string(const char *a, const char *b) -{ - return !strcmp(a ?: "", b ?: ""); -} - -static inline char *copy_string(const char *s) -{ - return (s && *s) ? strdup(s) : NULL; -} - -#include <libxml/tree.h> -#include <libxslt/transform.h> -#include <libxslt/xsltutils.h> - -#include "sha1.h" -#include "units.h" - -#ifdef __cplusplus -extern "C" { -#else -#include <stdbool.h> -#endif - -extern int last_xml_version; - -enum dive_comp_type {OC, CCR, PSCR, FREEDIVE, NUM_DC_TYPE}; // Flags (Open-circuit and Closed-circuit-rebreather) for setting dive computer type -enum cylinderuse {OC_GAS, DILUENT, OXYGEN, NUM_GAS_USE}; // The different uses for cylinders - -extern const char *cylinderuse_text[]; -extern const char *divemode_text[]; - -struct gasmix { - fraction_t o2; - fraction_t he; -}; - -typedef struct -{ - volume_t size; - pressure_t workingpressure; - const char *description; /* "LP85", "AL72", "AL80", "HP100+" or whatever */ -} cylinder_type_t; - -typedef struct -{ - cylinder_type_t type; - struct gasmix gasmix; - pressure_t start, end, sample_start, sample_end; - depth_t depth; - bool manually_added; - volume_t gas_used; - volume_t deco_gas_used; - enum cylinderuse cylinder_use; -} cylinder_t; - -typedef struct -{ - weight_t weight; - const char *description; /* "integrated", "belt", "ankle" */ -} weightsystem_t; - -/* - * Events are currently based straight on what libdivecomputer gives us. - * We need to wrap these into our own events at some point to remove some of the limitations. - */ -struct event { - struct event *next; - duration_t time; - int type; - /* This is the annoying libdivecomputer format. */ - int flags, value; - /* .. and this is our "extended" data for some event types */ - union { - /* - * Currently only for gas switch events. - * - * NOTE! The index may be -1, which means "unknown". In that - * case, the get_cylinder_index() function will give the best - * match with the cylinders in the dive based on gasmix. - */ - struct { - int index; - struct gasmix mix; - } gas; - }; - bool deleted; - char name[]; -}; - -extern int event_is_gaschange(struct event *ev); -extern int event_gasmix_redundant(struct event *ev); - -extern int get_pressure_units(int mb, const char **units); -extern double get_depth_units(int mm, int *frac, const char **units); -extern double get_volume_units(unsigned int ml, int *frac, const char **units); -extern double get_temp_units(unsigned int mk, const char **units); -extern double get_weight_units(unsigned int grams, int *frac, const char **units); -extern double get_vertical_speed_units(unsigned int mms, int *frac, const char **units); - -extern unsigned int units_to_depth(double depth); -extern int units_to_sac(double volume); - -/* Volume in mliter of a cylinder at pressure 'p' */ -extern int gas_volume(cylinder_t *cyl, pressure_t p); -extern double gas_compressibility_factor(struct gasmix *gas, double bar); - - -static inline int get_o2(const struct gasmix *mix) -{ - return mix->o2.permille ?: O2_IN_AIR; -} - -static inline int get_he(const struct gasmix *mix) -{ - return mix->he.permille; -} - -struct gas_pressures { - double o2, n2, he; -}; - -extern void fill_pressures(struct gas_pressures *pressures, const double amb_pressure, const struct gasmix *mix, double po2, enum dive_comp_type dctype); - -extern void sanitize_gasmix(struct gasmix *mix); -extern int gasmix_distance(const struct gasmix *a, const struct gasmix *b); -extern struct gasmix *get_gasmix_from_event(struct event *ev); - -static inline bool gasmix_is_air(const struct gasmix *gasmix) -{ - int o2 = gasmix->o2.permille; - int he = gasmix->he.permille; - return (he == 0) && (o2 == 0 || ((o2 >= O2_IN_AIR - 1) && (o2 <= O2_IN_AIR + 1))); -} - -/* Linear interpolation between 'a' and 'b', when we are 'part'way into the 'whole' distance from a to b */ -static inline int interpolate(int a, int b, int part, int whole) -{ - /* It is doubtful that we actually need floating point for this, but whatever */ - double x = (double)a * (whole - part) + (double)b * part; - return rint(x / whole); -} - -void get_gas_string(const struct gasmix *gasmix, char *text, int len); -const char *gasname(const struct gasmix *gasmix); - -struct sample // BASE TYPE BYTES UNITS RANGE DESCRIPTION -{ // --------- ----- ----- ----- ----------- - duration_t time; // uint32_t 4 seconds (0-68 yrs) elapsed dive time up to this sample - duration_t stoptime; // uint32_t 4 seconds (0-18 h) time duration of next deco stop - duration_t ndl; // uint32_t 4 seconds (0-18 h) time duration before no-deco limit - duration_t tts; // uint32_t 4 seconds (0-18 h) time duration to reach the surface - duration_t rbt; // uint32_t 4 seconds (0-18 h) remaining bottom time - depth_t depth; // int32_t 4 mm (0-2000 km) dive depth of this sample - depth_t stopdepth; // int32_t 4 mm (0-2000 km) depth of next deco stop - temperature_t temperature; // int32_t 4 mdegrK (0-2 MdegK) ambient temperature - pressure_t cylinderpressure; // int32_t 4 mbar (0-2 Mbar) main cylinder pressure - pressure_t o2cylinderpressure; // int32_t 4 mbar (0-2 Mbar) CCR o2 cylinder pressure (rebreather) - o2pressure_t setpoint; // uint16_t 2 mbar (0-65 bar) O2 partial pressure (will be setpoint) - o2pressure_t o2sensor[3]; // uint16_t 6 mbar (0-65 bar) Up to 3 PO2 sensor values (rebreather) - bearing_t bearing; // int16_t 2 degrees (-32k to 32k deg) compass bearing - uint8_t sensor; // uint8_t 1 sensorID (0-255) ID of cylinder pressure sensor - uint8_t cns; // uint8_t 1 % (0-255 %) cns% accumulated - uint8_t heartbeat; // uint8_t 1 beats/m (0-255) heart rate measurement - volume_t sac; // 4 ml/min predefined SAC - bool in_deco; // bool 1 y/n y/n this sample is part of deco - bool manually_entered; // bool 1 y/n y/n this sample was entered by the user, - // not calculated when planning a dive -}; // Total size of structure: 57 bytes, excluding padding at end - -struct divetag { - /* - * The name of the divetag. If a translation is available, name contains - * the translated tag - */ - char *name; - /* - * If a translation is available, we write the original tag to source. - * This enables us to write a non-localized tag to the xml file. - */ - char *source; -}; - -struct tag_entry { - struct divetag *tag; - struct tag_entry *next; -}; - -/* - * divetags are only stored once, each dive only contains - * a list of tag_entries which then point to the divetags - * in the global g_tag_list - */ - -extern struct tag_entry *g_tag_list; - -struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag); -struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list); -void dump_taglist(const char *intro, struct tag_entry *tl); - -/* - * Writes all divetags in tag_list to buffer, limited by the buffer's (len)gth. - * Returns the characters written - */ -int taglist_get_tagstring(struct tag_entry *tag_list, char *buffer, int len); - -/* cleans up a list: removes empty tags and duplicates */ -void taglist_cleanup(struct tag_entry **tag_list); - -void taglist_init_global(); -void taglist_free(struct tag_entry *tag_list); - -bool taglist_contains(struct tag_entry *tag_list, const char *tag); -bool taglist_equal(struct tag_entry *tl1, struct tag_entry *tl2); -int count_dives_with_tag(const char *tag); -int count_dives_with_person(const char *person); -int count_dives_with_location(const char *location); -int count_dives_with_suit(const char *suit); - -struct extra_data { - const char *key; - const char *value; - struct extra_data *next; -}; - -/* - * NOTE! The deviceid and diveid are model-specific *hashes* of - * whatever device identification that model may have. Different - * dive computers will have different identifying data, it could - * be a firmware number or a serial ID (in either string or in - * numeric format), and we do not care. - * - * The only thing we care about is that subsurface will hash - * that information the same way. So then you can check the ID - * of a dive computer by comparing the hashes for equality. - * - * A deviceid or diveid of zero is assumed to be "no ID". - */ -struct divecomputer { - timestamp_t when; - duration_t duration, surfacetime; - depth_t maxdepth, meandepth; - temperature_t airtemp, watertemp; - pressure_t surface_pressure; - enum dive_comp_type divemode; // dive computer type: OC(default) or CCR - uint8_t no_o2sensors; // rebreathers: number of O2 sensors used - int salinity; // kg per 10000 l - const char *model, *serial, *fw_version; - uint32_t deviceid, diveid; - int samples, alloc_samples; - struct sample *sample; - struct event *events; - struct extra_data *extra_data; - struct divecomputer *next; -}; - -#define MAX_CYLINDERS (8) -#define MAX_WEIGHTSYSTEMS (6) -#define W_IDX_PRIMARY 0 -#define W_IDX_SECONDARY 1 - -typedef enum { - TF_NONE, - NO_TRIP, - IN_TRIP, - ASSIGNED_TRIP, - NUM_TRIPFLAGS -} tripflag_t; - -typedef struct dive_trip -{ - timestamp_t when; - char *location; - char *notes; - struct dive *dives; - int nrdives; - int index; - unsigned expanded : 1, selected : 1, autogen : 1, fixup : 1; - struct dive_trip *next; -} dive_trip_t; - -/* List of dive trips (sorted by date) */ -extern dive_trip_t *dive_trip_list; -struct picture; -struct dive { - int number; - tripflag_t tripflag; - dive_trip_t *divetrip; - struct dive *next, **pprev; - bool selected; - bool hidden_by_filter; - bool downloaded; - timestamp_t when; - uint32_t dive_site_uuid; - char *notes; - char *divemaster, *buddy; - int rating; - int visibility; /* 0 - 5 star rating */ - cylinder_t cylinder[MAX_CYLINDERS]; - weightsystem_t weightsystem[MAX_WEIGHTSYSTEMS]; - char *suit; - int sac, otu, cns, maxcns; - - /* Calculated based on dive computer data */ - temperature_t mintemp, maxtemp, watertemp, airtemp; - depth_t maxdepth, meandepth; - pressure_t surface_pressure; - duration_t duration; - int salinity; // kg per 10000 l - - struct tag_entry *tag_list; - struct divecomputer dc; - int id; // unique ID for this dive - struct picture *picture_list; - int oxygen_cylinder_index, diluent_cylinder_index; // CCR dive cylinder indices - unsigned char git_id[20]; -}; - -static inline void invalidate_dive_cache(struct dive *dive) -{ - memset(dive->git_id, 0, 20); -} - -static inline bool dive_cache_is_valid(const struct dive *dive) -{ - static const unsigned char null_id[20] = { 0, }; - return !!memcmp(dive->git_id, null_id, 20); -} - -extern int get_cylinder_idx_by_use(struct dive *dive, enum cylinderuse cylinder_use_type); -extern void dc_cylinder_renumber(struct dive *dive, struct divecomputer *dc, int mapping[]); - -/* when selectively copying dive information, which parts should be copied? */ -struct dive_components { - unsigned int divesite : 1; - unsigned int notes : 1; - unsigned int divemaster : 1; - unsigned int buddy : 1; - unsigned int suit : 1; - unsigned int rating : 1; - unsigned int visibility : 1; - unsigned int tags : 1; - unsigned int cylinders : 1; - unsigned int weights : 1; -}; - -/* picture list and methods related to dive picture handling */ -struct picture { - char *filename; - char *hash; - offset_t offset; - degrees_t latitude; - degrees_t longitude; - struct picture *next; -}; - -#define FOR_EACH_PICTURE(_dive) \ - if (_dive) \ - for (struct picture *picture = (_dive)->picture_list; picture; picture = picture->next) - -#define FOR_EACH_PICTURE_NON_PTR(_divestruct) \ - for (struct picture *picture = (_divestruct).picture_list; picture; picture = picture->next) - -extern struct picture *alloc_picture(); -extern struct picture *clone_picture(struct picture *src); -extern bool dive_check_picture_time(struct dive *d, int shift_time, timestamp_t timestamp); -extern void dive_create_picture(struct dive *d, char *filename, int shift_time, bool match_all); -extern void dive_add_picture(struct dive *d, struct picture *newpic); -extern void dive_remove_picture(char *filename); -extern unsigned int dive_get_picture_count(struct dive *d); -extern bool picture_check_valid(char *filename, int shift_time); -extern void picture_load_exif_data(struct picture *p); -extern timestamp_t picture_get_timestamp(char *filename); -extern void dive_set_geodata_from_picture(struct dive *d, struct picture *pic); -extern void picture_free(struct picture *picture); - -extern int explicit_first_cylinder(struct dive *dive, struct divecomputer *dc); -extern int get_depth_at_time(struct divecomputer *dc, unsigned int time); - -static inline int get_surface_pressure_in_mbar(const struct dive *dive, bool non_null) -{ - int mbar = dive->surface_pressure.mbar; - if (!mbar && non_null) - mbar = SURFACE_PRESSURE; - return mbar; -} - -/* Pa = N/m^2 - so we determine the weight (in N) of the mass of 10m - * of water (and use standard salt water at 1.03kg per liter if we don't know salinity) - * and add that to the surface pressure (or to 1013 if that's unknown) */ -static inline int calculate_depth_to_mbar(int depth, pressure_t surface_pressure, int salinity) -{ - double specific_weight; - int mbar = surface_pressure.mbar; - - if (!mbar) - mbar = SURFACE_PRESSURE; - if (!salinity) - salinity = SEAWATER_SALINITY; - if (salinity < 500) - salinity += FRESHWATER_SALINITY; - specific_weight = salinity / 10000.0 * 0.981; - mbar += rint(depth / 10.0 * specific_weight); - return mbar; -} - -static inline int depth_to_mbar(int depth, struct dive *dive) -{ - return calculate_depth_to_mbar(depth, dive->surface_pressure, dive->salinity); -} - -static inline double depth_to_bar(int depth, struct dive *dive) -{ - return depth_to_mbar(depth, dive) / 1000.0; -} - -static inline double depth_to_atm(int depth, struct dive *dive) -{ - return mbar_to_atm(depth_to_mbar(depth, dive)); -} - -/* for the inverse calculation we use just the relative pressure - * (that's the one that some dive computers like the Uemis Zurich - * provide - for the other models that do this libdivecomputer has to - * take care of this, but the Uemis we support natively */ -static inline int rel_mbar_to_depth(int mbar, struct dive *dive) -{ - int cm; - double specific_weight = 1.03 * 0.981; - if (dive->dc.salinity) - specific_weight = dive->dc.salinity / 10000.0 * 0.981; - /* whole mbar gives us cm precision */ - cm = rint(mbar / specific_weight); - return cm * 10; -} - -static inline int mbar_to_depth(int mbar, struct dive *dive) -{ - pressure_t surface_pressure; - if (dive->surface_pressure.mbar) - surface_pressure = dive->surface_pressure; - else - surface_pressure.mbar = SURFACE_PRESSURE; - return rel_mbar_to_depth(mbar - surface_pressure.mbar, dive); -} - -/* MOD rounded to multiples of roundto mm */ -static inline depth_t gas_mod(struct gasmix *mix, pressure_t po2_limit, struct dive *dive, int roundto) { - depth_t rounded_depth; - - double depth = (double) mbar_to_depth(po2_limit.mbar * 1000 / get_o2(mix), dive); - rounded_depth.mm = rint(depth / roundto) * roundto; - return rounded_depth; -} - -#define SURFACE_THRESHOLD 750 /* somewhat arbitrary: only below 75cm is it really diving */ - -/* this is a global spot for a temporary dive structure that we use to - * be able to edit a dive without unintended side effects */ -extern struct dive edit_dive; - -extern short autogroup; -/* random threashold: three days without diving -> new trip - * this works very well for people who usually dive as part of a trip and don't - * regularly dive at a local facility; this is why trips are an optional feature */ -#define TRIP_THRESHOLD 3600 * 24 * 3 - -#define UNGROUPED_DIVE(_dive) ((_dive)->tripflag == NO_TRIP) -#define DIVE_IN_TRIP(_dive) ((_dive)->tripflag == IN_TRIP || (_dive)->tripflag == ASSIGNED_TRIP) -#define DIVE_NEEDS_TRIP(_dive) ((_dive)->tripflag == TF_NONE) - -extern void add_dive_to_trip(struct dive *, dive_trip_t *); - -extern void delete_single_dive(int idx); -extern void add_single_dive(int idx, struct dive *dive); - -extern void insert_trip(dive_trip_t **trip); - - -extern const struct units SI_units, IMPERIAL_units; -extern struct units xml_parsing_units; - -extern struct units *get_units(void); -extern int run_survey, verbose, quit, force_root; - -struct dive_table { - int nr, allocated, preexisting; - struct dive **dives; -}; - -extern struct dive_table dive_table, downloadTable; -extern struct dive displayed_dive; -extern struct dive_site displayed_dive_site; -extern int selected_dive; -extern unsigned int dc_number; -#define current_dive (get_dive(selected_dive)) -#define current_dc (get_dive_dc(current_dive, dc_number)) - -static inline struct dive *get_dive(int nr) -{ - if (nr >= dive_table.nr || nr < 0) - return NULL; - return dive_table.dives[nr]; -} - -static inline struct dive *get_dive_from_table(int nr, struct dive_table *dt) -{ - if (nr >= dt->nr || nr < 0) - return NULL; - return dt->dives[nr]; -} - -static inline struct dive_site *get_dive_site_for_dive(struct dive *dive) -{ - if (dive) - return get_dive_site_by_uuid(dive->dive_site_uuid); - return NULL; -} - -static inline char *get_dive_location(struct dive *dive) -{ - struct dive_site *ds = get_dive_site_by_uuid(dive->dive_site_uuid); - if (ds && ds->name) - return ds->name; - return NULL; -} - -static inline unsigned int number_of_computers(struct dive *dive) -{ - unsigned int total_number = 0; - struct divecomputer *dc = &dive->dc; - - if (!dive) - return 1; - - do { - total_number++; - dc = dc->next; - } while (dc); - return total_number; -} - -static inline struct divecomputer *get_dive_dc(struct dive *dive, int nr) -{ - struct divecomputer *dc = &dive->dc; - - while (nr-- > 0) { - dc = dc->next; - if (!dc) - return &dive->dc; - } - return dc; -} - -extern timestamp_t dive_endtime(const struct dive *dive); - -extern void make_first_dc(void); -extern unsigned int count_divecomputers(void); -extern void delete_current_divecomputer(void); - -/* - * Iterate over each dive, with the first parameter being the index - * iterator variable, and the second one being the dive one. - * - * I don't think anybody really wants the index, and we could make - * it local to the for-loop, but that would make us requires C99. - */ -#define for_each_dive(_i, _x) \ - for ((_i) = 0; ((_x) = get_dive(_i)) != NULL; (_i)++) - -#define for_each_dc(_dive, _dc) \ - for (_dc = &_dive->dc; _dc; _dc = _dc->next) - -#define for_each_gps_location(_i, _x) \ - for ((_i) = 0; ((_x) = get_gps_location(_i, &gps_location_table)) != NULL; (_i)++) - -static inline struct dive *get_dive_by_uniq_id(int id) -{ - int i; - struct dive *dive = NULL; - - for_each_dive (i, dive) { - if (dive->id == id) - break; - } -#ifdef DEBUG - if (dive == NULL) { - fprintf(stderr, "Invalid id %x passed to get_dive_by_diveid, try to fix the code\n", id); - exit(1); - } -#endif - return dive; -} - -static inline int get_idx_by_uniq_id(int id) -{ - int i; - struct dive *dive = NULL; - - for_each_dive (i, dive) { - if (dive->id == id) - break; - } -#ifdef DEBUG - if (dive == NULL) { - fprintf(stderr, "Invalid id %x passed to get_dive_by_diveid, try to fix the code\n", id); - exit(1); - } -#endif - return i; -} - -static inline bool dive_site_has_gps_location(struct dive_site *ds) -{ - return ds && (ds->latitude.udeg || ds->longitude.udeg); -} - -static inline int dive_has_gps_location(struct dive *dive) -{ - if (!dive) - return false; - return dive_site_has_gps_location(get_dive_site_by_uuid(dive->dive_site_uuid)); -} - -#ifdef __cplusplus -extern "C" { -#endif - -extern int report_error(const char *fmt, ...); -extern void report_message(const char *msg); -extern const char *get_error_string(void); - -extern struct dive *find_dive_including(timestamp_t when); -extern bool dive_within_time_range(struct dive *dive, timestamp_t when, timestamp_t offset); -extern bool time_during_dive_with_offset(struct dive *dive, timestamp_t when, timestamp_t offset); -struct dive *find_dive_n_near(timestamp_t when, int n, timestamp_t offset); - -/* Check if two dive computer entries are the exact same dive (-1=no/0=maybe/1=yes) */ -extern int match_one_dc(struct divecomputer *a, struct divecomputer *b); - -extern void parse_xml_init(void); -extern int parse_xml_buffer(const char *url, const char *buf, int size, struct dive_table *table, const char **params); -extern void parse_xml_exit(void); -extern void set_filename(const char *filename, bool force); - -extern int parse_dm4_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); -extern int parse_dm5_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); -extern int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); -extern int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); -extern int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); -extern int parse_dlf_buffer(unsigned char *buffer, size_t size); - -extern int check_git_sha(const char *filename); -extern int parse_file(const char *filename); -extern int parse_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate); -extern int parse_seabear_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate); -extern int parse_txt_file(const char *filename, const char *csv); -extern int parse_manual_file(const char *filename, char **params, int pnr); -extern int save_dives(const char *filename); -extern int save_dives_logic(const char *filename, bool select_only); -extern int save_dive(FILE *f, struct dive *dive); -extern int export_dives_xslt(const char *filename, const bool selected, const int units, const char *export_xslt); - -struct membuffer; -extern void save_one_dive_to_mb(struct membuffer *b, struct dive *dive); - -int cylinderuse_from_text(const char *text); - - -struct user_info { - const char *name; - const char *email; -}; - -extern void subsurface_user_info(struct user_info *); -extern int subsurface_rename(const char *path, const char *newpath); -extern int subsurface_dir_rename(const char *path, const char *newpath); -extern int subsurface_open(const char *path, int oflags, mode_t mode); -extern FILE *subsurface_fopen(const char *path, const char *mode); -extern void *subsurface_opendir(const char *path); -extern int subsurface_access(const char *path, int mode); -extern struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp); -extern int subsurface_zip_close(struct zip *zip); -extern void subsurface_console_init(bool dedicated); -extern void subsurface_console_exit(void); -extern bool subsurface_user_is_root(void); - -extern void shift_times(const timestamp_t amount); -extern timestamp_t get_times(); - -extern xsltStylesheetPtr get_stylesheet(const char *name); - -extern timestamp_t utc_mktime(struct tm *tm); -extern void utc_mkdate(timestamp_t, struct tm *tm); - -extern struct dive *alloc_dive(void); -extern void record_dive_to_table(struct dive *dive, struct dive_table *table); -extern void record_dive(struct dive *dive); -extern void clear_dive(struct dive *dive); -extern void copy_dive(struct dive *s, struct dive *d); -extern void selective_copy_dive(struct dive *s, struct dive *d, struct dive_components what, bool clear); -extern struct dive *clone_dive(struct dive *s); - -extern void clear_table(struct dive_table *table); - -extern struct sample *prepare_sample(struct divecomputer *dc); -extern void finish_sample(struct divecomputer *dc); - -extern bool has_hr_data(struct divecomputer *dc); - -extern void sort_table(struct dive_table *table); -extern struct dive *fixup_dive(struct dive *dive); -extern void fixup_dc_duration(struct divecomputer *dc); -extern int dive_getUniqID(struct dive *d); -extern unsigned int dc_airtemp(struct divecomputer *dc); -extern unsigned int dc_watertemp(struct divecomputer *dc); -extern int split_dive(struct dive *); -extern struct dive *merge_dives(struct dive *a, struct dive *b, int offset, bool prefer_downloaded); -extern struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded); -extern struct event *clone_event(const struct event *src_ev); -extern void copy_events(struct divecomputer *s, struct divecomputer *d); -extern void free_events(struct event *ev); -extern void copy_cylinders(struct dive *s, struct dive *d, bool used_only); -extern void copy_samples(struct divecomputer *s, struct divecomputer *d); -extern bool is_cylinder_used(struct dive *dive, int idx); -extern void fill_default_cylinder(cylinder_t *cyl); -extern void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int time, int idx); -extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name); -extern void remove_event(struct event *event); -extern void update_event_name(struct dive *d, struct event* event, char *name); -extern void add_extra_data(struct divecomputer *dc, const char *key, const char *value); -extern void per_cylinder_mean_depth(struct dive *dive, struct divecomputer *dc, int *mean, int *duration); -extern int get_cylinder_index(struct dive *dive, struct event *ev); -extern int nr_cylinders(struct dive *dive); -extern int nr_weightsystems(struct dive *dive); - -/* UI related protopypes */ - -// extern void report_error(GError* error); - -extern void add_cylinder_description(cylinder_type_t *); -extern void add_weightsystem_description(weightsystem_t *); -extern void remember_event(const char *eventname); -extern void invalidate_dive_cache(struct dive *dc); - -#if WE_DONT_USE_THIS /* this is a missing feature in Qt - selecting which events to display */ -extern int evn_foreach(void (*callback)(const char *, bool *, void *), void *data); -#endif /* WE_DONT_USE_THIS */ - -extern void clear_events(void); - -extern void set_dc_nickname(struct dive *dive); -extern void set_autogroup(bool value); -extern int total_weight(struct dive *); - -#ifdef __cplusplus -} -#endif - -#define DIVE_ERROR_PARSE 1 -#define DIVE_ERROR_PLAN 2 - -const char *weekday(int wday); -const char *monthname(int mon); - -#define UTF8_DEGREE "\xc2\xb0" -#define UTF8_DELTA "\xce\x94" -#define UTF8_UPWARDS_ARROW "\xE2\x86\x91" -#define UTF8_DOWNWARDS_ARROW "\xE2\x86\x93" -#define UTF8_AVERAGE "\xc3\xb8" -#define UTF8_SUBSCRIPT_2 "\xe2\x82\x82" -#define UTF8_WHITESTAR "\xe2\x98\x86" -#define UTF8_BLACKSTAR "\xe2\x98\x85" - -extern const char *existing_filename; -extern void subsurface_command_line_init(int *, char ***); -extern void subsurface_command_line_exit(int *, char ***); - -#define FRACTION(n, x) ((unsigned)(n) / (x)), ((unsigned)(n) % (x)) - -extern void add_segment(double pressure, const struct gasmix *gasmix, int period_in_seconds, int setpoint, const struct dive *dive, int sac); -extern void clear_deco(double surface_pressure); -extern void dump_tissues(void); -extern void set_gf(short gflow, short gfhigh, bool gf_low_at_maxdepth); -extern void cache_deco_state(char **datap); -extern void restore_deco_state(char *data); -extern void nuclear_regeneration(double time); -extern void vpmb_start_gradient(); -extern void vpmb_next_gradient(double deco_time, double surface_pressure); -extern double tissue_tolerance_calc(const struct dive *dive, double pressure); - -/* this should be converted to use our types */ -struct divedatapoint { - int time; - int depth; - struct gasmix gasmix; - int setpoint; - bool entered; - struct divedatapoint *next; -}; - -struct diveplan { - timestamp_t when; - int surface_pressure; /* mbar */ - int bottomsac; /* ml/min */ - int decosac; /* ml/min */ - int salinity; - short gflow; - short gfhigh; - struct divedatapoint *dp; -}; - -struct divedatapoint *plan_add_segment(struct diveplan *diveplan, int duration, int depth, struct gasmix gasmix, int po2, bool entered); -struct divedatapoint *create_dp(int time_incr, int depth, struct gasmix gasmix, int po2); -#if DEBUG_PLAN -void dump_plan(struct diveplan *diveplan); -#endif -bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer); -void calc_crushing_pressure(double pressure); -void vpmb_start_gradient(); - -void delete_single_dive(int idx); - -struct event *get_next_event(struct event *event, const char *name); - - -/* these structs holds the information that - * describes the cylinders / weight systems. - * they are global variables initialized in equipment.c - * used to fill the combobox in the add/edit cylinder - * dialog - */ - -struct tank_info_t { - const char *name; - int cuft, ml, psi, bar; -}; -extern struct tank_info_t tank_info[100]; - -struct ws_info_t { - const char *name; - int grams; -}; -extern struct ws_info_t ws_info[100]; - -extern bool cylinder_nodata(cylinder_t *cyl); -extern bool cylinder_none(void *_data); -extern bool weightsystem_none(void *_data); -extern bool no_weightsystems(weightsystem_t *ws); -extern bool weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2); -extern void remove_cylinder(struct dive *dive, int idx); -extern void remove_weightsystem(struct dive *dive, int idx); -extern void reset_cylinders(struct dive *dive, bool track_gas); - -/* - * String handling. - */ -#define STRTOD_NO_SIGN 0x01 -#define STRTOD_NO_DOT 0x02 -#define STRTOD_NO_COMMA 0x04 -#define STRTOD_NO_EXPONENT 0x08 -extern double strtod_flags(const char *str, const char **ptr, unsigned int flags); - -#define STRTOD_ASCII (STRTOD_NO_COMMA) - -#define ascii_strtod(str, ptr) strtod_flags(str, ptr, STRTOD_ASCII) - -extern void set_save_userid_local(short value); -extern void set_userid(char *user_id); -extern void set_informational_units(char *units); - -extern const char *get_dive_date_c_string(timestamp_t when); -extern void update_setpoint_events(struct divecomputer *dc); -#ifdef __cplusplus -} -#endif - -extern weight_t string_to_weight(const char *str); -extern depth_t string_to_depth(const char *str); -extern pressure_t string_to_pressure(const char *str); -extern volume_t string_to_volume(const char *str, pressure_t workp); -extern fraction_t string_to_fraction(const char *str); -extern void average_max_depth(struct diveplan *dive, int *avg_depth, int *max_depth); - -#include "pref.h" - -#endif // DIVE_H diff --git a/subsurface-core/divecomputer.cpp b/subsurface-core/divecomputer.cpp deleted file mode 100644 index e4081e1cd..000000000 --- a/subsurface-core/divecomputer.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "divecomputer.h" -#include "dive.h" - -#include <QSettings> - -const char *default_dive_computer_vendor; -const char *default_dive_computer_product; -const char *default_dive_computer_device; -int default_dive_computer_download_mode; -DiveComputerList dcList; - -DiveComputerList::DiveComputerList() -{ -} - -DiveComputerList::~DiveComputerList() -{ -} - -bool DiveComputerNode::operator==(const DiveComputerNode &a) const -{ - return model == a.model && - deviceId == a.deviceId && - firmware == a.firmware && - serialNumber == a.serialNumber && - nickName == a.nickName; -} - -bool DiveComputerNode::operator!=(const DiveComputerNode &a) const -{ - return !(*this == a); -} - -bool DiveComputerNode::changesValues(const DiveComputerNode &b) const -{ - if (model != b.model || deviceId != b.deviceId) { - qDebug("DiveComputerNodes were not for the same DC"); - return false; - } - return (firmware != b.firmware) || - (serialNumber != b.serialNumber) || - (nickName != b.nickName); -} - -const DiveComputerNode *DiveComputerList::getExact(const QString &m, uint32_t d) -{ - for (QMap<QString, DiveComputerNode>::iterator it = dcMap.find(m); it != dcMap.end() && it.key() == m; ++it) - if (it->deviceId == d) - return &*it; - return NULL; -} - -const DiveComputerNode *DiveComputerList::get(const QString &m) -{ - QMap<QString, DiveComputerNode>::iterator it = dcMap.find(m); - if (it != dcMap.end()) - return &*it; - return NULL; -} - -void DiveComputerNode::showchanges(const QString &n, const QString &s, const QString &f) const -{ - if (nickName != n) - qDebug("new nickname %s for DC model %s deviceId 0x%x", n.toUtf8().data(), model.toUtf8().data(), deviceId); - if (serialNumber != s) - qDebug("new serial number %s for DC model %s deviceId 0x%x", s.toUtf8().data(), model.toUtf8().data(), deviceId); - if (firmware != f) - qDebug("new firmware version %s for DC model %s deviceId 0x%x", f.toUtf8().data(), model.toUtf8().data(), deviceId); -} - -void DiveComputerList::addDC(QString m, uint32_t d, QString n, QString s, QString f) -{ - if (m.isEmpty() || d == 0) - return; - const DiveComputerNode *existNode = this->getExact(m, d); - - if (existNode) { - // Update any non-existent fields from the old entry - if (n.isEmpty()) - n = existNode->nickName; - if (s.isEmpty()) - s = existNode->serialNumber; - if (f.isEmpty()) - f = existNode->firmware; - - // Do all the old values match? - if (n == existNode->nickName && s == existNode->serialNumber && f == existNode->firmware) - return; - - // debugging: show changes - if (verbose) - existNode->showchanges(n, s, f); - dcMap.remove(m, *existNode); - } - - DiveComputerNode newNode(m, d, s, f, n); - dcMap.insert(m, newNode); -} - -extern "C" void create_device_node(const char *model, uint32_t deviceid, const char *serial, const char *firmware, const char *nickname) -{ - dcList.addDC(model, deviceid, nickname, serial, firmware); -} - -extern "C" bool compareDC(const DiveComputerNode &a, const DiveComputerNode &b) -{ - return a.deviceId < b.deviceId; -} - -extern "C" void call_for_each_dc (void *f, void (*callback)(void *, const char *, uint32_t, - const char *, const char *, const char *), - bool select_only) -{ - QList<DiveComputerNode> values = dcList.dcMap.values(); - qSort(values.begin(), values.end(), compareDC); - for (int i = 0; i < values.size(); i++) { - const DiveComputerNode *node = &values.at(i); - bool found = false; - if (select_only) { - int j; - struct dive *d; - for_each_dive (j, d) { - struct divecomputer *dc; - if (!d->selected) - continue; - for_each_dc(d, dc) { - if (dc->deviceid == node->deviceId) { - found = true; - break; - } - } - if (found) - break; - } - } else { - found = true; - } - if (found) - callback(f, node->model.toUtf8().data(), node->deviceId, node->nickName.toUtf8().data(), - node->serialNumber.toUtf8().data(), node->firmware.toUtf8().data()); - } -} - - -extern "C" int is_default_dive_computer(const char *vendor, const char *product) -{ - return default_dive_computer_vendor && !strcmp(vendor, default_dive_computer_vendor) && - default_dive_computer_product && !strcmp(product, default_dive_computer_product); -} - -extern "C" int is_default_dive_computer_device(const char *name) -{ - return default_dive_computer_device && !strcmp(name, default_dive_computer_device); -} - -void set_default_dive_computer(const char *vendor, const char *product) -{ - QSettings s; - - if (!vendor || !*vendor) - return; - if (!product || !*product) - return; - if (is_default_dive_computer(vendor, product)) - return; - - free((void *)default_dive_computer_vendor); - free((void *)default_dive_computer_product); - default_dive_computer_vendor = strdup(vendor); - default_dive_computer_product = strdup(product); - s.beginGroup("DiveComputer"); - s.setValue("dive_computer_vendor", vendor); - s.setValue("dive_computer_product", product); - s.endGroup(); -} - -void set_default_dive_computer_device(const char *name) -{ - QSettings s; - - if (!name || !*name) - return; - if (is_default_dive_computer_device(name)) - return; - - free((void *)default_dive_computer_device); - default_dive_computer_device = strdup(name); - s.beginGroup("DiveComputer"); - s.setValue("dive_computer_device", name); - s.endGroup(); -} - -void set_default_dive_computer_download_mode(int download_mode) -{ - QSettings s; - - default_dive_computer_download_mode = download_mode; - s.beginGroup("DiveComputer"); - s.setValue("dive_computer_download_mode", download_mode); - s.endGroup(); -} - -extern "C" void set_dc_nickname(struct dive *dive) -{ - if (!dive) - return; - - struct divecomputer *dc; - - for_each_dc (dive, dc) { - if (dc->model && *dc->model && dc->deviceid && - !dcList.getExact(dc->model, dc->deviceid)) { - // we don't have this one, yet - const DiveComputerNode *existNode = dcList.get(dc->model); - if (existNode) { - // we already have this model but a different deviceid - QString simpleNick(dc->model); - if (dc->deviceid == 0) - simpleNick.append(" (unknown deviceid)"); - else - simpleNick.append(" (").append(QString::number(dc->deviceid, 16)).append(")"); - dcList.addDC(dc->model, dc->deviceid, simpleNick); - } else { - dcList.addDC(dc->model, dc->deviceid); - } - } - } -} diff --git a/subsurface-core/divecomputer.h b/subsurface-core/divecomputer.h deleted file mode 100644 index 98d12ab8b..000000000 --- a/subsurface-core/divecomputer.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef DIVECOMPUTER_H -#define DIVECOMPUTER_H - -#include <QString> -#include <QMap> -#include <stdint.h> - -class DiveComputerNode { -public: - DiveComputerNode(QString m, uint32_t d, QString s, QString f, QString n) - : model(m), deviceId(d), serialNumber(s), firmware(f), nickName(n) {}; - bool operator==(const DiveComputerNode &a) const; - bool operator!=(const DiveComputerNode &a) const; - bool changesValues(const DiveComputerNode &b) const; - void showchanges(const QString &n, const QString &s, const QString &f) const; - QString model; - uint32_t deviceId; - QString serialNumber; - QString firmware; - QString nickName; -}; - -class DiveComputerList { -public: - DiveComputerList(); - ~DiveComputerList(); - const DiveComputerNode *getExact(const QString &m, uint32_t d); - const DiveComputerNode *get(const QString &m); - void addDC(QString m, uint32_t d, QString n = QString(), QString s = QString(), QString f = QString()); - DiveComputerNode matchDC(const QString &m, uint32_t d); - DiveComputerNode matchModel(const QString &m); - QMultiMap<QString, DiveComputerNode> dcMap; - QMultiMap<QString, DiveComputerNode> dcWorkingMap; -}; - -extern DiveComputerList dcList; - -#endif diff --git a/subsurface-core/divelist.c b/subsurface-core/divelist.c deleted file mode 100644 index 543d9e17b..000000000 --- a/subsurface-core/divelist.c +++ /dev/null @@ -1,1207 +0,0 @@ -/* divelist.c */ -/* core logic for the dive list - - * accessed through the following interfaces: - * - * dive_trip_t *dive_trip_list; - * unsigned int amount_selected; - * void dump_selection(void) - * dive_trip_t *find_trip_by_idx(int idx) - * int trip_has_selected_dives(dive_trip_t *trip) - * void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p) - * int total_weight(struct dive *dive) - * int get_divenr(struct dive *dive) - * double init_decompression(struct dive *dive) - * void update_cylinder_related_info(struct dive *dive) - * void dump_trip_list(void) - * dive_trip_t *find_matching_trip(timestamp_t when) - * void insert_trip(dive_trip_t **dive_trip_p) - * void remove_dive_from_trip(struct dive *dive) - * void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) - * dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) - * void autogroup_dives(void) - * void delete_single_dive(int idx) - * void add_single_dive(int idx, struct dive *dive) - * void merge_two_dives(struct dive *a, struct dive *b) - * void select_dive(int idx) - * void deselect_dive(int idx) - * void mark_divelist_changed(int changed) - * int unsaved_changes() - * void remove_autogen_trips() - */ -#include <unistd.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <math.h> -#include "gettext.h" -#include <assert.h> -#include <zip.h> -#include <libxslt/transform.h> - -#include "dive.h" -#include "divelist.h" -#include "display.h" -#include "planner.h" -#include "qthelperfromc.h" -#include "git-access.h" - -static short dive_list_changed = false; - -short autogroup = false; - -dive_trip_t *dive_trip_list; - -unsigned int amount_selected; - -#if DEBUG_SELECTION_TRACKING -void dump_selection(void) -{ - int i; - struct dive *dive; - - printf("currently selected are %u dives:", amount_selected); - for_each_dive(i, dive) { - if (dive->selected) - printf(" %d", i); - } - printf("\n"); -} -#endif - -void set_autogroup(bool value) -{ - /* if we keep the UI paradigm, this needs to toggle - * the checkbox on the autogroup menu item */ - autogroup = value; -} - -dive_trip_t *find_trip_by_idx(int idx) -{ - dive_trip_t *trip = dive_trip_list; - - if (idx >= 0) - return NULL; - idx = -idx; - while (trip) { - if (trip->index == idx) - return trip; - trip = trip->next; - } - return NULL; -} - -int trip_has_selected_dives(dive_trip_t *trip) -{ - struct dive *dive; - for (dive = trip->dives; dive; dive = dive->next) { - if (dive->selected) - return 1; - } - return 0; -} - -/* - * Get "maximal" dive gas for a dive. - * Rules: - * - Trimix trumps nitrox (highest He wins, O2 breaks ties) - * - Nitrox trumps air (even if hypoxic) - * These are the same rules as the inter-dive sorting rules. - */ -void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2max_p) -{ - int i; - int maxo2 = -1, maxhe = -1, mino2 = 1000; - - - for (i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = dive->cylinder + i; - int o2 = get_o2(&cyl->gasmix); - int he = get_he(&cyl->gasmix); - - if (!is_cylinder_used(dive, i)) - continue; - if (cylinder_none(cyl)) - continue; - if (o2 > maxo2) - maxo2 = o2; - if (he > maxhe) - goto newmax; - if (he < maxhe) - continue; - if (o2 <= maxo2) - continue; - newmax: - maxhe = he; - mino2 = o2; - } - /* All air? Show/sort as "air"/zero */ - if ((!maxhe && maxo2 == O2_IN_AIR && mino2 == maxo2) || - (maxo2 == -1 && maxhe == -1 && mino2 == 1000)) - maxo2 = mino2 = 0; - *o2_p = mino2; - *he_p = maxhe; - *o2max_p = maxo2; -} - -int total_weight(struct dive *dive) -{ - int i, total_grams = 0; - - if (dive) - for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) - total_grams += dive->weightsystem[i].weight.grams; - return total_grams; -} - -static int active_o2(struct dive *dive, struct divecomputer *dc, duration_t time) -{ - struct gasmix gas; - get_gas_at_time(dive, dc, time, &gas); - return get_o2(&gas); -} - -/* calculate OTU for a dive - this only takes the first divecomputer into account */ -static int calculate_otu(struct dive *dive) -{ - int i; - double otu = 0.0; - struct divecomputer *dc = &dive->dc; - - for (i = 1; i < dc->samples; i++) { - int t; - int po2; - struct sample *sample = dc->sample + i; - struct sample *psample = sample - 1; - t = sample->time.seconds - psample->time.seconds; - if (sample->setpoint.mbar) { - po2 = sample->setpoint.mbar; - } else { - int o2 = active_o2(dive, dc, sample->time); - po2 = o2 * depth_to_atm(sample->depth.mm, dive); - } - if (po2 >= 500) - otu += pow((po2 - 500) / 1000.0, 0.83) * t / 30.0; - } - return rint(otu); -} -/* calculate CNS for a dive - this only takes the first divecomputer into account */ -int const cns_table[][3] = { - /* po2, Maximum Single Exposure, Maximum 24 hour Exposure */ - { 1600, 45 * 60, 150 * 60 }, - { 1500, 120 * 60, 180 * 60 }, - { 1400, 150 * 60, 180 * 60 }, - { 1300, 180 * 60, 210 * 60 }, - { 1200, 210 * 60, 240 * 60 }, - { 1100, 240 * 60, 270 * 60 }, - { 1000, 300 * 60, 300 * 60 }, - { 900, 360 * 60, 360 * 60 }, - { 800, 450 * 60, 450 * 60 }, - { 700, 570 * 60, 570 * 60 }, - { 600, 720 * 60, 720 * 60 } -}; - -/* this only gets called if dive->maxcns == 0 which means we know that - * none of the divecomputers has tracked any CNS for us - * so we calculated it "by hand" */ -static int calculate_cns(struct dive *dive) -{ - int i, divenr; - size_t j; - double cns = 0.0; - struct divecomputer *dc = &dive->dc; - struct dive *prev_dive; - timestamp_t endtime; - - /* shortcut */ - if (dive->cns) - return dive->cns; - /* - * Do we start with a cns loading from a previous dive? - * Check if we did a dive 12 hours prior, and what cns we had from that. - * Then apply ha 90min halftime to see whats left. - */ - divenr = get_divenr(dive); - if (divenr) { - prev_dive = get_dive(divenr - 1); - if (prev_dive) { - endtime = prev_dive->when + prev_dive->duration.seconds; - if (dive->when < (endtime + 3600 * 12)) { - cns = calculate_cns(prev_dive); - cns = cns * 1 / pow(2, (dive->when - endtime) / (90.0 * 60.0)); - } - } - } - /* Caclulate the cns for each sample in this dive and sum them */ - for (i = 1; i < dc->samples; i++) { - int t; - int po2; - struct sample *sample = dc->sample + i; - struct sample *psample = sample - 1; - t = sample->time.seconds - psample->time.seconds; - if (sample->setpoint.mbar) { - po2 = sample->setpoint.mbar; - } else { - int o2 = active_o2(dive, dc, sample->time); - po2 = o2 * depth_to_atm(sample->depth.mm, dive); - } - /* CNS don't increse when below 500 matm */ - if (po2 < 500) - continue; - /* Find what table-row we should calculate % for */ - for (j = 1; j < sizeof(cns_table) / (sizeof(int) * 3); j++) - if (po2 > cns_table[j][0]) - break; - j--; - cns += ((double)t) / ((double)cns_table[j][1]) * 100; - } - /* save calculated cns in dive struct */ - dive->cns = cns; - return dive->cns; -} -/* - * Return air usage (in liters). - */ -static double calculate_airuse(struct dive *dive) -{ - int airuse = 0; - int i; - - for (i = 0; i < MAX_CYLINDERS; i++) { - pressure_t start, end; - cylinder_t *cyl = dive->cylinder + i; - - start = cyl->start.mbar ? cyl->start : cyl->sample_start; - end = cyl->end.mbar ? cyl->end : cyl->sample_end; - if (!end.mbar || start.mbar <= end.mbar) - continue; - - airuse += gas_volume(cyl, start) - gas_volume(cyl, end); - } - return airuse / 1000.0; -} - -/* this only uses the first divecomputer to calculate the SAC rate */ -static int calculate_sac(struct dive *dive) -{ - struct divecomputer *dc = &dive->dc; - double airuse, pressure, sac; - int duration, meandepth; - - airuse = calculate_airuse(dive); - if (!airuse) - return 0; - - duration = dc->duration.seconds; - if (!duration) - return 0; - - meandepth = dc->meandepth.mm; - if (!meandepth) - return 0; - - /* Mean pressure in ATM (SAC calculations are in atm*l/min) */ - pressure = depth_to_atm(meandepth, dive); - sac = airuse / pressure * 60 / duration; - - /* milliliters per minute.. */ - return sac * 1000; -} - -/* for now we do this based on the first divecomputer */ -static void add_dive_to_deco(struct dive *dive) -{ - struct divecomputer *dc = &dive->dc; - int i; - - if (!dc) - return; - for (i = 1; i < dc->samples; i++) { - struct sample *psample = dc->sample + i - 1; - struct sample *sample = dc->sample + i; - int t0 = psample->time.seconds; - int t1 = sample->time.seconds; - int j; - - for (j = t0; j < t1; j++) { - int depth = interpolate(psample->depth.mm, sample->depth.mm, j - t0, t1 - t0); - add_segment(depth_to_bar(depth, dive), - &dive->cylinder[sample->sensor].gasmix, 1, sample->setpoint.mbar, dive, dive->sac); - } - } -} - -int get_divenr(struct dive *dive) -{ - int i; - struct dive *d; - // tempting as it may be, don't die when called with dive=NULL - if (dive) - for_each_dive(i, d) { - if (d->id == dive->id) // don't compare pointers, we could be passing in a copy of the dive - return i; - } - return -1; -} - -int get_divesite_idx(struct dive_site *ds) -{ - int i; - struct dive_site *d; - // tempting as it may be, don't die when called with dive=NULL - if (ds) - for_each_dive_site(i, d) { - if (d->uuid == ds->uuid) // don't compare pointers, we could be passing in a copy of the dive - return i; - } - return -1; -} - -static struct gasmix air = { .o2.permille = O2_IN_AIR, .he.permille = 0 }; - -/* take into account previous dives until there is a 48h gap between dives */ -double init_decompression(struct dive *dive) -{ - int i, divenr = -1; - unsigned int surface_time; - timestamp_t when, lasttime = 0, laststart = 0; - bool deco_init = false; - double surface_pressure; - - if (!dive) - return 0.0; - - surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0; - divenr = get_divenr(dive); - when = dive->when; - i = divenr; - if (i < 0) { - i = dive_table.nr - 1; - while (i >= 0 && get_dive(i)->when > when) - --i; - i++; - } - while (i--) { - struct dive *pdive = get_dive(i); - /* we don't want to mix dives from different trips as we keep looking - * for how far back we need to go */ - if (dive->divetrip && pdive->divetrip != dive->divetrip) - continue; - if (!pdive || pdive->when >= when || pdive->when + pdive->duration.seconds + 48 * 60 * 60 < when) - break; - /* For simultaneous dives, only consider the first */ - if (pdive->when == laststart) - continue; - when = pdive->when; - lasttime = when + pdive->duration.seconds; - } - while (++i < (divenr >= 0 ? divenr : dive_table.nr)) { - struct dive *pdive = get_dive(i); - /* again skip dives from different trips */ - if (dive->divetrip && dive->divetrip != pdive->divetrip) - continue; - surface_pressure = get_surface_pressure_in_mbar(pdive, true) / 1000.0; - if (!deco_init) { - clear_deco(surface_pressure); - deco_init = true; -#if DECO_CALC_DEBUG & 2 - dump_tissues(); -#endif - } - add_dive_to_deco(pdive); - laststart = pdive->when; -#if DECO_CALC_DEBUG & 2 - printf("added dive #%d\n", pdive->number); - dump_tissues(); -#endif - if (pdive->when > lasttime) { - surface_time = pdive->when - lasttime; - lasttime = pdive->when + pdive->duration.seconds; - add_segment(surface_pressure, &air, surface_time, 0, dive, prefs.decosac); -#if DECO_CALC_DEBUG & 2 - printf("after surface intervall of %d:%02u\n", FRACTION(surface_time, 60)); - dump_tissues(); -#endif - } - } - /* add the final surface time */ - if (lasttime && dive->when > lasttime) { - surface_time = dive->when - lasttime; - surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0; - add_segment(surface_pressure, &air, surface_time, 0, dive, prefs.decosac); -#if DECO_CALC_DEBUG & 2 - printf("after surface intervall of %d:%02u\n", FRACTION(surface_time, 60)); - dump_tissues(); -#endif - } - if (!deco_init) { - surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0; - clear_deco(surface_pressure); -#if DECO_CALC_DEBUG & 2 - printf("no previous dive\n"); - dump_tissues(); -#endif - } - return tissue_tolerance_calc(dive, surface_pressure); -} - -void update_cylinder_related_info(struct dive *dive) -{ - if (dive != NULL) { - dive->sac = calculate_sac(dive); - dive->otu = calculate_otu(dive); - if (dive->maxcns == 0) - dive->maxcns = calculate_cns(dive); - } -} - -#define MAX_GAS_STRING 80 -#define UTF8_ELLIPSIS "\xE2\x80\xA6" - -/* callers needs to free the string */ -char *get_dive_gas_string(struct dive *dive) -{ - int o2, he, o2max; - char *buffer = malloc(MAX_GAS_STRING); - - if (buffer) { - get_dive_gas(dive, &o2, &he, &o2max); - o2 = (o2 + 5) / 10; - he = (he + 5) / 10; - o2max = (o2max + 5) / 10; - - if (he) - if (o2 == o2max) - snprintf(buffer, MAX_GAS_STRING, "%d/%d", o2, he); - else - snprintf(buffer, MAX_GAS_STRING, "%d/%d" UTF8_ELLIPSIS "%d%%", o2, he, o2max); - else if (o2) - if (o2 == o2max) - snprintf(buffer, MAX_GAS_STRING, "%d%%", o2); - else - snprintf(buffer, MAX_GAS_STRING, "%d" UTF8_ELLIPSIS "%d%%", o2, o2max); - else - strcpy(buffer, translate("gettextFromC", "air")); - } - return buffer; -} - -/* - * helper functions for dive_trip handling - */ -#ifdef DEBUG_TRIP -void dump_trip_list(void) -{ - dive_trip_t *trip; - int i = 0; - timestamp_t last_time = 0; - - for (trip = dive_trip_list; trip; trip = trip->next) { - struct tm tm; - utc_mkdate(trip->when, &tm); - if (trip->when < last_time) - printf("\n\ndive_trip_list OUT OF ORDER!!!\n\n\n"); - printf("%s trip %d to \"%s\" on %04u-%02u-%02u %02u:%02u:%02u (%d dives - %p)\n", - trip->autogen ? "autogen " : "", - ++i, trip->location, - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, - trip->nrdives, trip); - last_time = trip->when; - } - printf("-----\n"); -} -#endif - -/* this finds the last trip that at or before the time given */ -dive_trip_t *find_matching_trip(timestamp_t when) -{ - dive_trip_t *trip = dive_trip_list; - - if (!trip || trip->when > when) { -#ifdef DEBUG_TRIP - printf("no matching trip\n"); -#endif - return NULL; - } - while (trip->next && trip->next->when <= when) - trip = trip->next; -#ifdef DEBUG_TRIP - { - struct tm tm; - utc_mkdate(trip->when, &tm); - printf("found trip %p @ %04d-%02d-%02d %02d:%02d:%02d\n", - trip, - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec); - } -#endif - return trip; -} - -/* insert the trip into the dive_trip_list - but ensure you don't have - * two trips for the same date; but if you have, make sure you don't - * keep the one with less information */ -void insert_trip(dive_trip_t **dive_trip_p) -{ - dive_trip_t *dive_trip = *dive_trip_p; - dive_trip_t **p = &dive_trip_list; - dive_trip_t *trip; - struct dive *divep; - - /* Walk the dive trip list looking for the right location.. */ - while ((trip = *p) != NULL && trip->when < dive_trip->when) - p = &trip->next; - - if (trip && trip->when == dive_trip->when) { - if (!trip->location) - trip->location = dive_trip->location; - if (!trip->notes) - trip->notes = dive_trip->notes; - divep = dive_trip->dives; - while (divep) { - add_dive_to_trip(divep, trip); - divep = divep->next; - } - *dive_trip_p = trip; - } else { - dive_trip->next = trip; - *p = dive_trip; - } -#ifdef DEBUG_TRIP - dump_trip_list(); -#endif -} - -static void delete_trip(dive_trip_t *trip) -{ - dive_trip_t **p, *tmp; - - assert(!trip->dives); - - /* Remove the trip from the list of trips */ - p = &dive_trip_list; - while ((tmp = *p) != NULL) { - if (tmp == trip) { - *p = trip->next; - break; - } - p = &tmp->next; - } - - /* .. and free it */ - free(trip->location); - free(trip->notes); - free(trip); -} - -void find_new_trip_start_time(dive_trip_t *trip) -{ - struct dive *dive = trip->dives; - timestamp_t when = dive->when; - - while ((dive = dive->next) != NULL) { - if (dive->when < when) - when = dive->when; - } - trip->when = when; -} - -/* check if we have a trip right before / after this dive */ -bool is_trip_before_after(struct dive *dive, bool before) -{ - int idx = get_idx_by_uniq_id(dive->id); - if (before) { - if (idx > 0 && get_dive(idx - 1)->divetrip) - return true; - } else { - if (idx < dive_table.nr - 1 && get_dive(idx + 1)->divetrip) - return true; - } - return false; -} - -struct dive *first_selected_dive() -{ - int idx; - struct dive *d; - - for_each_dive (idx, d) { - if (d->selected) - return d; - } - return NULL; -} - -struct dive *last_selected_dive() -{ - int idx; - struct dive *d, *ret = NULL; - - for_each_dive (idx, d) { - if (d->selected) - ret = d; - } - return ret; -} - -void remove_dive_from_trip(struct dive *dive, short was_autogen) -{ - struct dive *next, **pprev; - dive_trip_t *trip = dive->divetrip; - - if (!trip) - return; - - /* Remove the dive from the trip's list of dives */ - next = dive->next; - pprev = dive->pprev; - *pprev = next; - if (next) - next->pprev = pprev; - - dive->divetrip = NULL; - if (was_autogen) - dive->tripflag = TF_NONE; - else - dive->tripflag = NO_TRIP; - assert(trip->nrdives > 0); - if (!--trip->nrdives) - delete_trip(trip); - else if (trip->when == dive->when) - find_new_trip_start_time(trip); -} - -void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) -{ - if (dive->divetrip == trip) - return; - remove_dive_from_trip(dive, false); - trip->nrdives++; - dive->divetrip = trip; - dive->tripflag = ASSIGNED_TRIP; - - /* Add it to the trip's list of dives*/ - dive->next = trip->dives; - if (dive->next) - dive->next->pprev = &dive->next; - trip->dives = dive; - dive->pprev = &trip->dives; - - if (dive->when && trip->when > dive->when) - trip->when = dive->when; -} - -dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) -{ - dive_trip_t *dive_trip = calloc(1, sizeof(dive_trip_t)); - - dive_trip->when = dive->when; - dive_trip->location = copy_string(get_dive_location(dive)); - insert_trip(&dive_trip); - - dive->tripflag = IN_TRIP; - add_dive_to_trip(dive, dive_trip); - return dive_trip; -} - -/* - * Walk the dives from the oldest dive, and see if we can autogroup them - */ -void autogroup_dives(void) -{ - int i; - struct dive *dive, *lastdive = NULL; - - for_each_dive(i, dive) { - dive_trip_t *trip; - - if (dive->divetrip) { - lastdive = dive; - continue; - } - - if (!DIVE_NEEDS_TRIP(dive)) { - lastdive = NULL; - continue; - } - - /* Do we have a trip we can combine this into? */ - if (lastdive && dive->when < lastdive->when + TRIP_THRESHOLD) { - dive_trip_t *trip = lastdive->divetrip; - add_dive_to_trip(dive, trip); - if (get_dive_location(dive) && !trip->location) - trip->location = copy_string(get_dive_location(dive)); - lastdive = dive; - continue; - } - - lastdive = dive; - trip = create_and_hookup_trip_from_dive(dive); - trip->autogen = 1; - } - -#ifdef DEBUG_TRIP - dump_trip_list(); -#endif -} - -/* this implements the mechanics of removing the dive from the table, - * but doesn't deal with updating dive trips, etc */ -void delete_single_dive(int idx) -{ - int i; - struct dive *dive = get_dive(idx); - if (!dive) - return; /* this should never happen */ - remove_dive_from_trip(dive, false); - if (dive->selected) - deselect_dive(idx); - for (i = idx; i < dive_table.nr - 1; i++) - dive_table.dives[i] = dive_table.dives[i + 1]; - dive_table.dives[--dive_table.nr] = NULL; - /* free all allocations */ - free(dive->dc.sample); - free((void *)dive->notes); - free((void *)dive->divemaster); - free((void *)dive->buddy); - free((void *)dive->suit); - taglist_free(dive->tag_list); - free(dive); -} - -struct dive **grow_dive_table(struct dive_table *table) -{ - int nr = table->nr, allocated = table->allocated; - struct dive **dives = table->dives; - - if (nr >= allocated) { - allocated = (nr + 32) * 3 / 2; - dives = realloc(dives, allocated * sizeof(struct dive *)); - if (!dives) - exit(1); - table->dives = dives; - table->allocated = allocated; - } - return dives; -} - -void add_single_dive(int idx, struct dive *dive) -{ - int i; - grow_dive_table(&dive_table); - dive_table.nr++; - if (dive->selected) - amount_selected++; - - if (idx < 0) { - // convert an idx of -1 so we do insert-in-chronological-order - idx = dive_table.nr - 1; - for (int i = 0; i < dive_table.nr - 1; i++) { - if (dive->when <= dive_table.dives[i]->when) { - idx = i; - break; - } - } - } - - for (i = idx; i < dive_table.nr; i++) { - struct dive *tmp = dive_table.dives[i]; - dive_table.dives[i] = dive; - dive = tmp; - } -} - -bool consecutive_selected() -{ - struct dive *d; - int i; - bool consecutive = true; - bool firstfound = false; - bool lastfound = false; - - if (amount_selected == 0 || amount_selected == 1) - return true; - - for_each_dive(i, d) { - if (d->selected) { - if (!firstfound) - firstfound = true; - else if (lastfound) - consecutive = false; - } else if (firstfound) { - lastfound = true; - } - } - return consecutive; -} - -/* - * Merge two dives. 'a' is always before 'b' in the dive list - * (and thus in time). - */ -struct dive *merge_two_dives(struct dive *a, struct dive *b) -{ - struct dive *res; - int i, j, nr, nrdiff; - int id; - - if (!a || !b) - return NULL; - - id = a->id; - i = get_divenr(a); - j = get_divenr(b); - if (i < 0 || j < 0) - // something is wrong with those dives. Bail - return NULL; - res = merge_dives(a, b, b->when - a->when, false); - if (!res) - return NULL; - - /* - * If 'a' and 'b' were numbered, and in proper order, - * then the resulting dive will get the first number, - * and the subsequent dives will be renumbered by the - * difference. - * - * So if you had a dive list 1 3 6 7 8, and you - * merge 1 and 3, the resulting numbered list will - * be 1 4 5 6, because we assume that there were - * some missing dives (originally dives 4 and 5), - * that now will still be missing (dives 2 and 3 - * in the renumbered world). - * - * Obviously the normal case is that everything is - * consecutive, and the difference will be 1, so the - * above example is not supposed to be normal. - */ - nrdiff = 0; - nr = a->number; - if (a->number && b->number > a->number) { - res->number = nr; - nrdiff = b->number - nr; - } - - add_single_dive(i, res); - delete_single_dive(i + 1); - delete_single_dive(j); - // now make sure that we keep the id of the first dive. - // why? - // because this way one of the previously selected ids is still around - res->id = id; - - // renumber dives from merged one in advance by difference between - // merged dives numbers. Do not renumber if actual number is zero. - for (; j < dive_table.nr; j++) { - struct dive *dive = dive_table.dives[j]; - int newnr; - - if (!dive->number) - continue; - newnr = dive->number - nrdiff; - - /* - * Don't renumber stuff that isn't in order! - * - * So if the new dive number isn't larger than the - * previous dive number, just stop here. - */ - if (newnr <= nr) - break; - dive->number = newnr; - nr = newnr; - } - - mark_divelist_changed(true); - return res; -} - -void select_dive(int idx) -{ - struct dive *dive = get_dive(idx); - if (dive) { - /* never select an invalid dive that isn't displayed */ - if (!dive->selected) { - dive->selected = 1; - amount_selected++; - } - selected_dive = idx; - } -} - -void deselect_dive(int idx) -{ - struct dive *dive = get_dive(idx); - if (dive && dive->selected) { - dive->selected = 0; - if (amount_selected) - amount_selected--; - if (selected_dive == idx && amount_selected > 0) { - /* pick a different dive as selected */ - while (--selected_dive >= 0) { - dive = get_dive(selected_dive); - if (dive && dive->selected) - return; - } - selected_dive = idx; - while (++selected_dive < dive_table.nr) { - dive = get_dive(selected_dive); - if (dive && dive->selected) - return; - } - } - if (amount_selected == 0) - selected_dive = -1; - } -} - -void deselect_dives_in_trip(struct dive_trip *trip) -{ - struct dive *dive; - if (!trip) - return; - for (dive = trip->dives; dive; dive = dive->next) - deselect_dive(get_divenr(dive)); -} - -void select_dives_in_trip(struct dive_trip *trip) -{ - struct dive *dive; - if (!trip) - return; - for (dive = trip->dives; dive; dive = dive->next) - if (!dive->hidden_by_filter) - select_dive(get_divenr(dive)); -} - -void filter_dive(struct dive *d, bool shown) -{ - if (!d) - return; - d->hidden_by_filter = !shown; - if (!shown && d->selected) - deselect_dive(get_divenr(d)); -} - - -/* This only gets called with non-NULL trips. - * It does not combine notes or location, just picks the first one - * (or the second one if the first one is empty */ -void combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b) -{ - if (same_string(trip_a->location, "") && trip_b->location) { - free(trip_a->location); - trip_a->location = strdup(trip_b->location); - } - if (same_string(trip_a->notes, "") && trip_b->notes) { - free(trip_a->notes); - trip_a->notes = strdup(trip_b->notes); - } - /* this also removes the dives from trip_b and eventually - * calls delete_trip(trip_b) when the last dive has been moved */ - while (trip_b->dives) - add_dive_to_trip(trip_b->dives, trip_a); -} - -void mark_divelist_changed(int changed) -{ - dive_list_changed = changed; - updateWindowTitle(); -} - -int unsaved_changes() -{ - return dive_list_changed; -} - -void remove_autogen_trips() -{ - int i; - struct dive *dive; - - for_each_dive(i, dive) { - dive_trip_t *trip = dive->divetrip; - - if (trip && trip->autogen) - remove_dive_from_trip(dive, true); - } -} - -/* - * When adding dives to the dive table, we try to renumber - * the new dives based on any old dives in the dive table. - * - * But we only do it if: - * - * - there are no dives in the dive table - * - * OR - * - * - the last dive in the old dive table was numbered - * - * - all the new dives are strictly at the end (so the - * "last dive" is at the same location in the dive table - * after re-sorting the dives. - * - * - none of the new dives have any numbers - * - * This catches the common case of importing new dives from - * a dive computer, and gives them proper numbers based on - * your old dive list. But it tries to be very conservative - * and not give numbers if there is *any* question about - * what the numbers should be - in which case you need to do - * a manual re-numbering. - */ -static void try_to_renumber(struct dive *last, int preexisting) -{ - int i, nr; - - /* - * If the new dives aren't all strictly at the end, - * we're going to expect the user to do a manual - * renumbering. - */ - if (preexisting && get_dive(preexisting - 1) != last) - return; - - /* - * If any of the new dives already had a number, - * we'll have to do a manual renumbering. - */ - for (i = preexisting; i < dive_table.nr; i++) { - struct dive *dive = get_dive(i); - if (dive->number) - return; - } - - /* - * Ok, renumber.. - */ - if (last) - nr = last->number; - else - nr = 0; - for (i = preexisting; i < dive_table.nr; i++) { - struct dive *dive = get_dive(i); - dive->number = ++nr; - } -} - -void process_dives(bool is_imported, bool prefer_imported) -{ - int i; - int preexisting = dive_table.preexisting; - bool did_merge = false; - struct dive *last; - - /* check if we need a nickname for the divecomputer for newly downloaded dives; - * since we know they all came from the same divecomputer we just check for the - * first one */ - if (preexisting < dive_table.nr && dive_table.dives[preexisting]->downloaded) - set_dc_nickname(dive_table.dives[preexisting]); - else - /* they aren't downloaded, so record / check all new ones */ - for (i = preexisting; i < dive_table.nr; i++) - set_dc_nickname(dive_table.dives[i]); - - for (i = preexisting; i < dive_table.nr; i++) - dive_table.dives[i]->downloaded = true; - - /* This does the right thing for -1: NULL */ - last = get_dive(preexisting - 1); - - sort_table(&dive_table); - - for (i = 1; i < dive_table.nr; i++) { - struct dive **pp = &dive_table.dives[i - 1]; - struct dive *prev = pp[0]; - struct dive *dive = pp[1]; - struct dive *merged; - int id; - - /* only try to merge overlapping dives - or if one of the dives has - * zero duration (that might be a gps marker from the webservice) */ - if (prev->duration.seconds && dive->duration.seconds && - prev->when + prev->duration.seconds < dive->when) - continue; - - merged = try_to_merge(prev, dive, prefer_imported); - if (!merged) - continue; - - // remember the earlier dive's id - id = prev->id; - - /* careful - we might free the dive that last points to. Oops... */ - if (last == prev || last == dive) - last = merged; - - /* Redo the new 'i'th dive */ - i--; - add_single_dive(i, merged); - delete_single_dive(i + 1); - delete_single_dive(i + 1); - // keep the id or the first dive for the merged dive - merged->id = id; - - /* this means the table was changed */ - did_merge = true; - } - /* make sure no dives are still marked as downloaded */ - for (i = 1; i < dive_table.nr; i++) - dive_table.dives[i]->downloaded = false; - - if (is_imported) { - /* If there are dives in the table, are they numbered */ - if (!last || last->number) - try_to_renumber(last, preexisting); - - /* did we add dives or divecomputers to the dive table? */ - if (did_merge || preexisting < dive_table.nr) { - mark_divelist_changed(true); - } - } -} - -void set_dive_nr_for_current_dive() -{ - if (dive_table.nr == 1) - current_dive->number = 1; - else if (selected_dive == dive_table.nr - 1 && get_dive(dive_table.nr - 2)->number) - current_dive->number = get_dive(dive_table.nr - 2)->number + 1; -} - -static int min_datafile_version; - -int get_min_datafile_version() -{ - return min_datafile_version; -} - -void reset_min_datafile_version() -{ - min_datafile_version = 0; -} - -void report_datafile_version(int version) -{ - if (min_datafile_version == 0 || min_datafile_version > version) - min_datafile_version = version; -} - -void clear_dive_file_data() -{ - while (dive_table.nr) - delete_single_dive(0); - while (dive_site_table.nr) - delete_dive_site(get_dive_site(0)->uuid); - - clear_dive(&displayed_dive); - clear_dive_site(&displayed_dive_site); - - free((void *)existing_filename); - existing_filename = NULL; - - reset_min_datafile_version(); - saved_git_id = ""; -} diff --git a/subsurface-core/divelist.h b/subsurface-core/divelist.h deleted file mode 100644 index 5bae09cff..000000000 --- a/subsurface-core/divelist.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef DIVELIST_H -#define DIVELIST_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* this is used for both git and xml format */ -#define DATAFORMAT_VERSION 3 - -struct dive; - -extern void update_cylinder_related_info(struct dive *); -extern void mark_divelist_changed(int); -extern int unsaved_changes(void); -extern void remove_autogen_trips(void); -extern double init_decompression(struct dive *dive); - -/* divelist core logic functions */ -extern void process_dives(bool imported, bool prefer_imported); -extern char *get_dive_gas_string(struct dive *dive); - -extern dive_trip_t *find_trip_by_idx(int idx); - -struct dive **grow_dive_table(struct dive_table *table); -extern int trip_has_selected_dives(dive_trip_t *trip); -extern void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p); -extern int get_divenr(struct dive *dive); -extern int get_divesite_idx(struct dive_site *ds); -extern dive_trip_t *find_matching_trip(timestamp_t when); -extern void remove_dive_from_trip(struct dive *dive, short was_autogen); -extern dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive); -extern void autogroup_dives(void); -extern struct dive *merge_two_dives(struct dive *a, struct dive *b); -extern bool consecutive_selected(); -extern void select_dive(int idx); -extern void deselect_dive(int idx); -extern void select_dives_in_trip(struct dive_trip *trip); -extern void deselect_dives_in_trip(struct dive_trip *trip); -extern void filter_dive(struct dive *d, bool shown); -extern void combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b); -extern void find_new_trip_start_time(dive_trip_t *trip); -extern struct dive *first_selected_dive(); -extern struct dive *last_selected_dive(); -extern bool is_trip_before_after(struct dive *dive, bool before); -extern void set_dive_nr_for_current_dive(); - -int get_min_datafile_version(); -void reset_min_datafile_version(); -void report_datafile_version(int version); -void clear_dive_file_data(); - -#ifdef DEBUG_TRIP -extern void dump_selection(void); -extern void dump_trip_list(void); -#endif - -#ifdef __cplusplus -} -#endif - -#endif // DIVELIST_H diff --git a/subsurface-core/divelogexportlogic.cpp b/subsurface-core/divelogexportlogic.cpp deleted file mode 100644 index af5157f4a..000000000 --- a/subsurface-core/divelogexportlogic.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include <QString> -#include <QFile> -#include <QFileInfo> -#include <QDir> -#include <QSettings> -#include <QTextStream> -#include "divelogexportlogic.h" -#include "helpers.h" -#include "units.h" -#include "statistics.h" -#include "save-html.h" - -void file_copy_and_overwrite(const QString &fileName, const QString &newName) -{ - QFile file(newName); - if (file.exists()) - file.remove(); - QFile::copy(fileName, newName); -} - -void exportHTMLsettings(const QString &filename, struct htmlExportSetting &hes) -{ - QString fontSize = hes.fontSize; - QString fontFamily = hes.fontFamily; - QFile file(filename); - file.open(QIODevice::WriteOnly | QIODevice::Text); - QTextStream out(&file); - out << "settings = {\"fontSize\":\"" << fontSize << "\",\"fontFamily\":\"" << fontFamily << "\",\"listOnly\":\"" - << hes.listOnly << "\",\"subsurfaceNumbers\":\"" << hes.subsurfaceNumbers << "\","; - //save units preferences - if (prefs.unit_system == METRIC) { - out << "\"unit_system\":\"Meteric\""; - } else if (prefs.unit_system == IMPERIAL) { - out << "\"unit_system\":\"Imperial\""; - } else { - QVariant v; - QString length, pressure, volume, temperature, weight; - length = prefs.units.length == units::METERS ? "METER" : "FEET"; - pressure = prefs.units.pressure == units::BAR ? "BAR" : "PSI"; - volume = prefs.units.volume == units::LITER ? "LITER" : "CUFT"; - temperature = prefs.units.temperature == units::CELSIUS ? "CELSIUS" : "FAHRENHEIT"; - weight = prefs.units.weight == units::KG ? "KG" : "LBS"; - out << "\"unit_system\":\"Personalize\","; - out << "\"units\":{\"depth\":\"" << length << "\",\"pressure\":\"" << pressure << "\",\"volume\":\"" << volume << "\",\"temperature\":\"" << temperature << "\",\"weight\":\"" << weight << "\"}"; - } - out << "}"; - file.close(); -} - -static void exportHTMLstatisticsTotal(QTextStream &out, stats_t *total_stats) -{ - out << "{"; - out << "\"YEAR\":\"Total\","; - out << "\"DIVES\":\"" << total_stats->selection_size << "\","; - out << "\"TOTAL_TIME\":\"" << get_time_string(total_stats->total_time.seconds, 0) << "\","; - out << "\"AVERAGE_TIME\":\"--\","; - out << "\"SHORTEST_TIME\":\"--\","; - out << "\"LONGEST_TIME\":\"--\","; - out << "\"AVG_DEPTH\":\"--\","; - out << "\"MIN_DEPTH\":\"--\","; - out << "\"MAX_DEPTH\":\"--\","; - out << "\"AVG_SAC\":\"--\","; - out << "\"MIN_SAC\":\"--\","; - out << "\"MAX_SAC\":\"--\","; - out << "\"AVG_TEMP\":\"--\","; - out << "\"MIN_TEMP\":\"--\","; - out << "\"MAX_TEMP\":\"--\","; - out << "},"; -} - - -static void exportHTMLstatistics(const QString filename, struct htmlExportSetting &hes) -{ - QFile file(filename); - file.open(QIODevice::WriteOnly | QIODevice::Text); - QTextStream out(&file); - - stats_t total_stats; - - total_stats.selection_size = 0; - total_stats.total_time.seconds = 0; - - int i = 0; - out << "divestat=["; - if (hes.yearlyStatistics) { - while (stats_yearly != NULL && stats_yearly[i].period) { - out << "{"; - out << "\"YEAR\":\"" << stats_yearly[i].period << "\","; - out << "\"DIVES\":\"" << stats_yearly[i].selection_size << "\","; - out << "\"TOTAL_TIME\":\"" << get_time_string(stats_yearly[i].total_time.seconds, 0) << "\","; - out << "\"AVERAGE_TIME\":\"" << get_minutes(stats_yearly[i].total_time.seconds / stats_yearly[i].selection_size) << "\","; - out << "\"SHORTEST_TIME\":\"" << get_minutes(stats_yearly[i].shortest_time.seconds) << "\","; - out << "\"LONGEST_TIME\":\"" << get_minutes(stats_yearly[i].longest_time.seconds) << "\","; - out << "\"AVG_DEPTH\":\"" << get_depth_string(stats_yearly[i].avg_depth) << "\","; - out << "\"MIN_DEPTH\":\"" << get_depth_string(stats_yearly[i].min_depth) << "\","; - out << "\"MAX_DEPTH\":\"" << get_depth_string(stats_yearly[i].max_depth) << "\","; - out << "\"AVG_SAC\":\"" << get_volume_string(stats_yearly[i].avg_sac) << "\","; - out << "\"MIN_SAC\":\"" << get_volume_string(stats_yearly[i].min_sac) << "\","; - out << "\"MAX_SAC\":\"" << get_volume_string(stats_yearly[i].max_sac) << "\","; - if ( stats_yearly[i].combined_count ) - out << "\"AVG_TEMP\":\"" << QString::number(stats_yearly[i].combined_temp / stats_yearly[i].combined_count, 'f', 1) << "\","; - else - out << "\"AVG_TEMP\":\"0.0\","; - out << "\"MIN_TEMP\":\"" << ( stats_yearly[i].min_temp == 0 ? 0 : get_temp_units(stats_yearly[i].min_temp, NULL)) << "\","; - out << "\"MAX_TEMP\":\"" << ( stats_yearly[i].max_temp == 0 ? 0 : get_temp_units(stats_yearly[i].max_temp, NULL)) << "\","; - out << "},"; - total_stats.selection_size += stats_yearly[i].selection_size; - total_stats.total_time.seconds += stats_yearly[i].total_time.seconds; - i++; - } - exportHTMLstatisticsTotal(out, &total_stats); - } - out << "]"; - file.close(); - -} - -void exportHtmlInitLogic(const QString &filename, struct htmlExportSetting &hes) -{ - QString photosDirectory; - QFile file(filename); - QFileInfo info(file); - QDir mainDir = info.absoluteDir(); - mainDir.mkdir(file.fileName() + "_files"); - QString exportFiles = file.fileName() + "_files"; - - QString json_dive_data = exportFiles + QDir::separator() + "file.js"; - QString json_settings = exportFiles + QDir::separator() + "settings.js"; - QString translation = exportFiles + QDir::separator() + "translation.js"; - QString stat_file = exportFiles + QDir::separator() + "stat.js"; - exportFiles += "/"; - - if (hes.exportPhotos) { - photosDirectory = exportFiles + QDir::separator() + "photos" + QDir::separator(); - mainDir.mkdir(photosDirectory); - } - - - exportHTMLsettings(json_settings, hes); - exportHTMLstatistics(stat_file, hes); - export_translation(translation.toUtf8().data()); - - export_HTML(qPrintable(json_dive_data), qPrintable(photosDirectory), hes.selectedOnly, hes.listOnly); - - QString searchPath = getSubsurfaceDataPath("theme"); - if (searchPath.isEmpty()) - return; - - searchPath += QDir::separator(); - - file_copy_and_overwrite(searchPath + "dive_export.html", filename); - file_copy_and_overwrite(searchPath + "list_lib.js", exportFiles + "list_lib.js"); - file_copy_and_overwrite(searchPath + "poster.png", exportFiles + "poster.png"); - file_copy_and_overwrite(searchPath + "jqplot.highlighter.min.js", exportFiles + "jqplot.highlighter.min.js"); - file_copy_and_overwrite(searchPath + "jquery.jqplot.min.js", exportFiles + "jquery.jqplot.min.js"); - file_copy_and_overwrite(searchPath + "jqplot.canvasAxisTickRenderer.min.js", exportFiles + "jqplot.canvasAxisTickRenderer.min.js"); - file_copy_and_overwrite(searchPath + "jqplot.canvasTextRenderer.min.js", exportFiles + "jqplot.canvasTextRenderer.min.js"); - file_copy_and_overwrite(searchPath + "jquery.min.js", exportFiles + "jquery.min.js"); - file_copy_and_overwrite(searchPath + "jquery.jqplot.css", exportFiles + "jquery.jqplot.css"); - file_copy_and_overwrite(searchPath + hes.themeFile, exportFiles + "theme.css"); -} diff --git a/subsurface-core/divelogexportlogic.h b/subsurface-core/divelogexportlogic.h deleted file mode 100644 index 84f09c362..000000000 --- a/subsurface-core/divelogexportlogic.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef DIVELOGEXPORTLOGIC_H -#define DIVELOGEXPORTLOGIC_H - -struct htmlExportSetting { - bool exportPhotos; - bool selectedOnly; - bool listOnly; - QString fontFamily; - QString fontSize; - int themeSelection; - bool subsurfaceNumbers; - bool yearlyStatistics; - QString themeFile; -}; - -void file_copy_and_overwrite(const QString &fileName, const QString &newName); -void exportHtmlInitLogic(const QString &filename, struct htmlExportSetting &hes); - -#endif // DIVELOGEXPORTLOGIC_H - diff --git a/subsurface-core/divesite.c b/subsurface-core/divesite.c deleted file mode 100644 index e9eed2a07..000000000 --- a/subsurface-core/divesite.c +++ /dev/null @@ -1,337 +0,0 @@ -/* divesite.c */ -#include "divesite.h" -#include "dive.h" -#include "divelist.h" - -#include <math.h> - -struct dive_site_table dive_site_table; - -/* there could be multiple sites of the same name - return the first one */ -uint32_t get_dive_site_uuid_by_name(const char *name, struct dive_site **dsp) -{ - int i; - struct dive_site *ds; - for_each_dive_site (i, ds) { - if (same_string(ds->name, name)) { - if (dsp) - *dsp = ds; - return ds->uuid; - } - } - return 0; -} - -/* there could be multiple sites at the same GPS fix - return the first one */ -uint32_t get_dive_site_uuid_by_gps(degrees_t latitude, degrees_t longitude, struct dive_site **dsp) -{ - int i; - struct dive_site *ds; - for_each_dive_site (i, ds) { - if (ds->latitude.udeg == latitude.udeg && ds->longitude.udeg == longitude.udeg) { - if (dsp) - *dsp = ds; - return ds->uuid; - } - } - return 0; -} - - -/* to avoid a bug where we have two dive sites with different name and the same GPS coordinates - * and first get the gps coordinates (reading a V2 file) and happen to get back "the other" name, - * this function allows us to verify if a very specific name/GPS combination already exists */ -uint32_t get_dive_site_uuid_by_gps_and_name(char *name, degrees_t latitude, degrees_t longitude) -{ - int i; - struct dive_site *ds; - for_each_dive_site (i, ds) { - if (ds->latitude.udeg == latitude.udeg && ds->longitude.udeg == longitude.udeg && same_string(ds->name, name)) - return ds->uuid; - } - return 0; -} - -// Calculate the distance in meters between two coordinates. -unsigned int get_distance(degrees_t lat1, degrees_t lon1, degrees_t lat2, degrees_t lon2) -{ - double lat2_r = udeg_to_radians(lat2.udeg); - double lat_d_r = udeg_to_radians(lat2.udeg-lat1.udeg); - double lon_d_r = udeg_to_radians(lon2.udeg-lon1.udeg); - - double a = sin(lat_d_r/2) * sin(lat_d_r/2) + - cos(lat2_r) * cos(lat2_r) * sin(lon_d_r/2) * sin(lon_d_r/2); - double c = 2 * atan2(sqrt(a), sqrt(1.0 - a)); - - // Earth radious in metres - return 6371000 * c; -} - -/* find the closest one, no more than distance meters away - if more than one at same distance, pick the first */ -uint32_t get_dive_site_uuid_by_gps_proximity(degrees_t latitude, degrees_t longitude, int distance, struct dive_site **dsp) -{ - int i; - int uuid = 0; - struct dive_site *ds; - unsigned int cur_distance, min_distance = distance; - for_each_dive_site (i, ds) { - if (dive_site_has_gps_location(ds) && - (cur_distance = get_distance(ds->latitude, ds->longitude, latitude, longitude)) < min_distance) { - min_distance = cur_distance; - uuid = ds->uuid; - if (dsp) - *dsp = ds; - } - } - return uuid; -} - -/* try to create a uniqe ID - fingers crossed */ -static uint32_t dive_site_getUniqId() -{ - uint32_t id = 0; - - while (id == 0 || get_dive_site_by_uuid(id)) { - id = rand() & 0xff; - id |= (rand() & 0xff) << 8; - id |= (rand() & 0xff) << 16; - id |= (rand() & 0xff) << 24; - } - - return id; -} - -/* we never allow a second dive site with the same uuid */ -struct dive_site *alloc_or_get_dive_site(uint32_t uuid) -{ - struct dive_site *ds; - if (uuid) { - if ((ds = get_dive_site_by_uuid(uuid)) != NULL) { - fprintf(stderr, "PROBLEM: refusing to create dive site with the same uuid %08x\n", uuid); - return ds; - } - } - int nr = dive_site_table.nr; - int allocated = dive_site_table.allocated; - struct dive_site **sites = dive_site_table.dive_sites; - - if (nr >= allocated) { - allocated = (nr + 32) * 3 / 2; - sites = realloc(sites, allocated * sizeof(struct dive_site *)); - if (!sites) - exit(1); - dive_site_table.dive_sites = sites; - dive_site_table.allocated = allocated; - } - ds = calloc(1, sizeof(*ds)); - if (!ds) - exit(1); - sites[nr] = ds; - dive_site_table.nr = nr + 1; - // we should always be called with a valid uuid except in the special - // case where we want to copy a dive site into the memory we allocated - // here - then we need to pass in 0 and create a temporary uuid here - // (just so things are always consistent) - if (uuid) - ds->uuid = uuid; - else - ds->uuid = dive_site_getUniqId(); - return ds; -} - -int nr_of_dives_at_dive_site(uint32_t uuid, bool select_only) -{ - int j; - int nr = 0; - struct dive *d; - for_each_dive(j, d) { - if (d->dive_site_uuid == uuid && (!select_only || d->selected)) { - nr++; - } - } - return nr; -} - -bool is_dive_site_used(uint32_t uuid, bool select_only) -{ - int j; - bool found = false; - struct dive *d; - for_each_dive(j, d) { - if (d->dive_site_uuid == uuid && (!select_only || d->selected)) { - found = true; - break; - } - } - return found; -} - -void delete_dive_site(uint32_t id) -{ - int nr = dive_site_table.nr; - for (int i = 0; i < nr; i++) { - struct dive_site *ds = get_dive_site(i); - if (ds->uuid == id) { - free(ds->name); - free(ds->notes); - free(ds); - if (nr - 1 > i) - memmove(&dive_site_table.dive_sites[i], - &dive_site_table.dive_sites[i+1], - (nr - 1 - i) * sizeof(dive_site_table.dive_sites[0])); - dive_site_table.nr = nr - 1; - break; - } - } -} - -uint32_t create_divesite_uuid(const char *name, timestamp_t divetime) -{ - if (name == NULL) - name =""; - unsigned char hash[20]; - SHA_CTX ctx; - SHA1_Init(&ctx); - SHA1_Update(&ctx, &divetime, sizeof(timestamp_t)); - SHA1_Update(&ctx, name, strlen(name)); - SHA1_Final(hash, &ctx); - // now return the first 32 of the 160 bit hash - return *(uint32_t *)hash; -} - -/* allocate a new site and add it to the table */ -uint32_t create_dive_site(const char *name, timestamp_t divetime) -{ - uint32_t uuid = create_divesite_uuid(name, divetime); - struct dive_site *ds = alloc_or_get_dive_site(uuid); - ds->name = copy_string(name); - - return uuid; -} - -/* same as before, but with current time if no current_dive is present */ -uint32_t create_dive_site_from_current_dive(const char *name) -{ - if (current_dive != NULL) { - return create_dive_site(name, current_dive->when); - } else { - timestamp_t when; - time_t now = time(0); - when = utc_mktime(localtime(&now)); - return create_dive_site(name, when); - } -} - -/* same as before, but with GPS data */ -uint32_t create_dive_site_with_gps(const char *name, degrees_t latitude, degrees_t longitude, timestamp_t divetime) -{ - uint32_t uuid = create_divesite_uuid(name, divetime); - struct dive_site *ds = alloc_or_get_dive_site(uuid); - ds->name = copy_string(name); - ds->latitude = latitude; - ds->longitude = longitude; - - return ds->uuid; -} - -/* a uuid is always present - but if all the other fields are empty, the dive site is pointless */ -bool dive_site_is_empty(struct dive_site *ds) -{ - return same_string(ds->name, "") && - same_string(ds->description, "") && - same_string(ds->notes, "") && - ds->latitude.udeg == 0 && - ds->longitude.udeg == 0; -} - -void copy_dive_site(struct dive_site *orig, struct dive_site *copy) -{ - free(copy->name); - free(copy->notes); - free(copy->description); - - copy->latitude = orig->latitude; - copy->longitude = orig->longitude; - copy->name = copy_string(orig->name); - copy->notes = copy_string(orig->notes); - copy->description = copy_string(orig->description); - copy->uuid = orig->uuid; - if (orig->taxonomy.category == NULL) { - free_taxonomy(©->taxonomy); - } else { - if (copy->taxonomy.category == NULL) - copy->taxonomy.category = alloc_taxonomy(); - for (int i = 0; i < TC_NR_CATEGORIES; i++) { - if (i < copy->taxonomy.nr) - free((void *)copy->taxonomy.category[i].value); - if (i < orig->taxonomy.nr) { - copy->taxonomy.category[i] = orig->taxonomy.category[i]; - copy->taxonomy.category[i].value = copy_string(orig->taxonomy.category[i].value); - } - } - copy->taxonomy.nr = orig->taxonomy.nr; - } -} - -void clear_dive_site(struct dive_site *ds) -{ - free(ds->name); - free(ds->notes); - free(ds->description); - ds->name = 0; - ds->notes = 0; - ds->description = 0; - ds->latitude.udeg = 0; - ds->longitude.udeg = 0; - ds->uuid = 0; - ds->taxonomy.nr = 0; - free_taxonomy(&ds->taxonomy); -} - -void merge_dive_sites(uint32_t ref, uint32_t* uuids, int count) -{ - int curr_dive, i; - struct dive *d; - for(i = 0; i < count; i++){ - if (uuids[i] == ref) - continue; - - for_each_dive(curr_dive, d) { - if (d->dive_site_uuid != uuids[i] ) - continue; - d->dive_site_uuid = ref; - } - } - - for(i = 0; i < count; i++) { - if (uuids[i] == ref) - continue; - delete_dive_site(uuids[i]); - } - mark_divelist_changed(true); -} - -uint32_t find_or_create_dive_site_with_name(const char *name, timestamp_t divetime) -{ - int i; - struct dive_site *ds; - for_each_dive_site(i,ds) { - if (same_string(name, ds->name)) - break; - } - if (ds) - return ds->uuid; - return create_dive_site(name, divetime); -} - -static int compare_sites(const void *_a, const void *_b) -{ - const struct dive_site *a = (const struct dive_site *)*(void **)_a; - const struct dive_site *b = (const struct dive_site *)*(void **)_b; - return a->uuid > b->uuid ? 1 : a->uuid == b->uuid ? 0 : -1; -} - -void dive_site_table_sort() -{ - qsort(dive_site_table.dive_sites, dive_site_table.nr, sizeof(struct dive_site *), compare_sites); -} diff --git a/subsurface-core/divesite.cpp b/subsurface-core/divesite.cpp deleted file mode 100644 index ae102a14b..000000000 --- a/subsurface-core/divesite.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "divesite.h" -#include "pref.h" - -QString constructLocationTags(uint32_t ds_uuid) -{ - QString locationTag; - struct dive_site *ds = get_dive_site_by_uuid(ds_uuid); - - if (!ds || !ds->taxonomy.nr) - return locationTag; - - locationTag = "<small><small>(tags: "; - QString connector; - for (int i = 0; i < 3; i++) { - if (prefs.geocoding.category[i] == TC_NONE) - continue; - for (int j = 0; j < TC_NR_CATEGORIES; j++) { - if (ds->taxonomy.category[j].category == prefs.geocoding.category[i]) { - QString tag = ds->taxonomy.category[j].value; - if (!tag.isEmpty()) { - locationTag += connector + tag; - connector = " / "; - } - break; - } - } - } - - locationTag += ")</small></small>"; - return locationTag; -} diff --git a/subsurface-core/divesite.h b/subsurface-core/divesite.h deleted file mode 100644 index f18b2e8e8..000000000 --- a/subsurface-core/divesite.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef DIVESITE_H -#define DIVESITE_H - -#include "units.h" -#include "taxonomy.h" -#include <stdlib.h> - -#ifdef __cplusplus -#include <QString> -extern "C" { -#else -#include <stdbool.h> -#endif - -struct dive_site -{ - uint32_t uuid; - char *name; - degrees_t latitude, longitude; - char *description; - char *notes; - struct taxonomy_data taxonomy; -}; - -struct dive_site_table { - int nr, allocated; - struct dive_site **dive_sites; -}; - -extern struct dive_site_table dive_site_table; - -static inline struct dive_site *get_dive_site(int nr) -{ - if (nr >= dive_site_table.nr || nr < 0) - return NULL; - return dive_site_table.dive_sites[nr]; -} - -/* iterate over each dive site */ -#define for_each_dive_site(_i, _x) \ - for ((_i) = 0; ((_x) = get_dive_site(_i)) != NULL; (_i)++) - -static inline struct dive_site *get_dive_site_by_uuid(uint32_t uuid) -{ - int i; - struct dive_site *ds; - for_each_dive_site (i, ds) - if (ds->uuid == uuid) - return get_dive_site(i); - return NULL; -} - -void dive_site_table_sort(); -struct dive_site *alloc_or_get_dive_site(uint32_t uuid); -int nr_of_dives_at_dive_site(uint32_t uuid, bool select_only); -bool is_dive_site_used(uint32_t uuid, bool select_only); -void delete_dive_site(uint32_t id); -uint32_t create_dive_site(const char *name, timestamp_t divetime); -uint32_t create_dive_site_from_current_dive(const char *name); -uint32_t create_dive_site_with_gps(const char *name, degrees_t latitude, degrees_t longitude, timestamp_t divetime); -uint32_t get_dive_site_uuid_by_name(const char *name, struct dive_site **dsp); -uint32_t get_dive_site_uuid_by_gps(degrees_t latitude, degrees_t longitude, struct dive_site **dsp); -uint32_t get_dive_site_uuid_by_gps_and_name(char *name, degrees_t latitude, degrees_t longitude); -uint32_t get_dive_site_uuid_by_gps_proximity(degrees_t latitude, degrees_t longitude, int distance, struct dive_site **dsp); -bool dive_site_is_empty(struct dive_site *ds); -void copy_dive_site(struct dive_site *orig, struct dive_site *copy); -void clear_dive_site(struct dive_site *ds); -unsigned int get_distance(degrees_t lat1, degrees_t lon1, degrees_t lat2, degrees_t lon2); -uint32_t find_or_create_dive_site_with_name(const char *name, timestamp_t divetime); -void merge_dive_sites(uint32_t ref, uint32_t *uuids, int count); - -#define INVALID_DIVE_SITE_NAME "development use only - not a valid dive site name" - -#ifdef __cplusplus -} -QString constructLocationTags(uint32_t ds_uuid); - -#endif - -#endif // DIVESITE_H diff --git a/subsurface-core/divesitehelpers.cpp b/subsurface-core/divesitehelpers.cpp deleted file mode 100644 index 3542f96fa..000000000 --- a/subsurface-core/divesitehelpers.cpp +++ /dev/null @@ -1,208 +0,0 @@ -// -// infrastructure to deal with dive sites -// - -#include "divesitehelpers.h" - -#include "divesite.h" -#include "helpers.h" -#include "membuffer.h" -#include <QJsonDocument> -#include <QJsonArray> -#include <QJsonObject> -#include <QNetworkReply> -#include <QNetworkRequest> -#include <QNetworkAccessManager> -#include <QUrlQuery> -#include <QEventLoop> -#include <QTimer> - -struct GeoLookupInfo { - degrees_t lat; - degrees_t lon; - uint32_t uuid; -}; - -QVector<GeoLookupInfo> geo_lookup_data; - -ReverseGeoLookupThread* ReverseGeoLookupThread::instance() { - static ReverseGeoLookupThread* self = new ReverseGeoLookupThread(); - return self; -} - -ReverseGeoLookupThread::ReverseGeoLookupThread(QObject *obj) : QThread(obj) -{ -} - -void ReverseGeoLookupThread::run() { - if (geo_lookup_data.isEmpty()) - return; - - QNetworkRequest request; - QNetworkAccessManager *rgl = new QNetworkAccessManager(); - QEventLoop loop; - QString mapquestURL("http://open.mapquestapi.com/nominatim/v1/reverse.php?format=json&accept-language=%1&lat=%2&lon=%3"); - QString geonamesURL("http://api.geonames.org/findNearbyPlaceNameJSON?language=%1&lat=%2&lng=%3&radius=50&username=dirkhh"); - QString geonamesOceanURL("http://api.geonames.org/oceanJSON?language=%1&lat=%2&lng=%3&radius=50&username=dirkhh"); - QString divelogsURL("https://www.divelogs.de/mapsearch_divespotnames.php?lat=%1&lng=%2&radius=50"); - QTimer timer; - - request.setRawHeader("Accept", "text/json"); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - - Q_FOREACH (const GeoLookupInfo& info, geo_lookup_data ) { - struct dive_site *ds = info.uuid ? get_dive_site_by_uuid(info.uuid) : &displayed_dive_site; - - // first check the findNearbyPlaces API from geonames - that should give us country, state, city - request.setUrl(geonamesURL.arg(uiLanguage(NULL)).arg(info.lat.udeg / 1000000.0).arg(info.lon.udeg / 1000000.0)); - - QNetworkReply *reply = rgl->get(request); - timer.setSingleShot(true); - connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - timer.start(5000); // 5 secs. timeout - loop.exec(); - - if(timer.isActive()) { - timer.stop(); - if(reply->error() > 0) { - report_error("got error accessing geonames.org: %s", qPrintable(reply->errorString())); - goto clear_reply; - } - int v = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (v < 200 || v >= 300) - goto clear_reply; - QByteArray fullReply = reply->readAll(); - QJsonParseError errorObject; - QJsonDocument jsonDoc = QJsonDocument::fromJson(fullReply, &errorObject); - if (errorObject.error != QJsonParseError::NoError) { - report_error("error parsing geonames.org response: %s", qPrintable(errorObject.errorString())); - goto clear_reply; - } - QJsonObject obj = jsonDoc.object(); - QVariant geoNamesObject = obj.value("geonames").toVariant(); - QVariantList geoNames = geoNamesObject.toList(); - if (geoNames.count() > 0) { - QVariantMap firstData = geoNames.at(0).toMap(); - int ri = 0, l3 = -1, lt = -1; - if (ds->taxonomy.category == NULL) { - ds->taxonomy.category = alloc_taxonomy(); - } else { - // clear out the data (except for the ocean data) - int ocean; - if ((ocean = taxonomy_index_for_category(&ds->taxonomy, TC_OCEAN)) > 0) { - ds->taxonomy.category[0] = ds->taxonomy.category[ocean]; - ds->taxonomy.nr = 1; - } else { - // ocean is -1 if there is no such entry, and we didn't copy above - // if ocean is 0, so the following gets us the correct count - ds->taxonomy.nr = ocean + 1; - } - } - // get all the data - OCEAN is special, so start at COUNTRY - for (int j = TC_COUNTRY; j < TC_NR_CATEGORIES; j++) { - if (firstData[taxonomy_api_names[j]].isValid()) { - ds->taxonomy.category[ri].category = j; - ds->taxonomy.category[ri].origin = taxonomy::GEOCODED; - free((void*)ds->taxonomy.category[ri].value); - ds->taxonomy.category[ri].value = copy_string(qPrintable(firstData[taxonomy_api_names[j]].toString())); - ri++; - } - } - ds->taxonomy.nr = ri; - l3 = taxonomy_index_for_category(&ds->taxonomy, TC_ADMIN_L3); - lt = taxonomy_index_for_category(&ds->taxonomy, TC_LOCALNAME); - if (l3 == -1 && lt != -1) { - // basically this means we did get a local name (what we call town), but just like most places - // we didn't get an adminName_3 - which in some regions is the actual city that town belongs to, - // then we copy the town into the city - ds->taxonomy.category[ri].value = copy_string(ds->taxonomy.category[lt].value); - ds->taxonomy.category[ri].origin = taxonomy::COPIED; - ds->taxonomy.category[ri].category = TC_ADMIN_L3; - ds->taxonomy.nr++; - } - mark_divelist_changed(true); - } else { - report_error("geonames.org did not provide reverse lookup information"); - qDebug() << "no reverse geo lookup; geonames returned\n" << fullReply; - } - } else { - report_error("timeout accessing geonames.org"); - disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - reply->abort(); - } - // next check the oceans API to figure out the body of water - request.setUrl(geonamesOceanURL.arg(uiLanguage(NULL)).arg(info.lat.udeg / 1000000.0).arg(info.lon.udeg / 1000000.0)); - reply = rgl->get(request); - connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - timer.start(5000); // 5 secs. timeout - loop.exec(); - if(timer.isActive()) { - timer.stop(); - if(reply->error() > 0) { - report_error("got error accessing oceans API of geonames.org: %s", qPrintable(reply->errorString())); - goto clear_reply; - } - int v = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (v < 200 || v >= 300) - goto clear_reply; - QByteArray fullReply = reply->readAll(); - QJsonParseError errorObject; - QJsonDocument jsonDoc = QJsonDocument::fromJson(fullReply, &errorObject); - if (errorObject.error != QJsonParseError::NoError) { - report_error("error parsing geonames.org response: %s", qPrintable(errorObject.errorString())); - goto clear_reply; - } - QJsonObject obj = jsonDoc.object(); - QVariant oceanObject = obj.value("ocean").toVariant(); - QVariantMap oceanName = oceanObject.toMap(); - if (oceanName["name"].isValid()) { - int idx; - if (ds->taxonomy.category == NULL) - ds->taxonomy.category = alloc_taxonomy(); - idx = taxonomy_index_for_category(&ds->taxonomy, TC_OCEAN); - if (idx == -1) - idx = ds->taxonomy.nr; - if (idx < TC_NR_CATEGORIES) { - ds->taxonomy.category[idx].category = TC_OCEAN; - ds->taxonomy.category[idx].origin = taxonomy::GEOCODED; - ds->taxonomy.category[idx].value = copy_string(qPrintable(oceanName["name"].toString())); - if (idx == ds->taxonomy.nr) - ds->taxonomy.nr++; - } - mark_divelist_changed(true); - } - } else { - report_error("timeout accessing geonames.org"); - disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - reply->abort(); - } - -clear_reply: - reply->deleteLater(); - } - rgl->deleteLater(); -} - -void ReverseGeoLookupThread::lookup(dive_site *ds) -{ - if (!ds) - return; - GeoLookupInfo info; - info.lat = ds->latitude; - info.lon = ds->longitude; - info.uuid = ds->uuid; - - geo_lookup_data.clear(); - geo_lookup_data.append(info); - run(); -} - -extern "C" void add_geo_information_for_lookup(degrees_t latitude, degrees_t longitude, uint32_t uuid) { - GeoLookupInfo info; - info.lat = latitude; - info.lon = longitude; - info.uuid = uuid; - - geo_lookup_data.append(info); -} diff --git a/subsurface-core/divesitehelpers.h b/subsurface-core/divesitehelpers.h deleted file mode 100644 index a08069bc0..000000000 --- a/subsurface-core/divesitehelpers.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef DIVESITEHELPERS_H -#define DIVESITEHELPERS_H - -#include "units.h" -#include <QThread> - -class ReverseGeoLookupThread : public QThread { -Q_OBJECT -public: - static ReverseGeoLookupThread *instance(); - void lookup(struct dive_site *ds); - void run() Q_DECL_OVERRIDE; - -private: - ReverseGeoLookupThread(QObject *parent = 0); -}; - -#endif // DIVESITEHELPERS_H diff --git a/subsurface-core/equipment.c b/subsurface-core/equipment.c deleted file mode 100644 index 9f3e49039..000000000 --- a/subsurface-core/equipment.c +++ /dev/null @@ -1,238 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -/* equipment.c */ -#include <stdio.h> -#include <string.h> -#include <stdlib.h> -#include <stdarg.h> -#include <time.h> -#include "gettext.h" -#include "dive.h" -#include "display.h" -#include "divelist.h" - -/* placeholders for a few functions that we need to redesign for the Qt UI */ -void add_cylinder_description(cylinder_type_t *type) -{ - const char *desc; - int i; - - desc = type->description; - if (!desc) - return; - for (i = 0; i < 100 && tank_info[i].name != NULL; i++) { - if (strcmp(tank_info[i].name, desc) == 0) - return; - } - if (i < 100) { - // FIXME: leaked on exit - tank_info[i].name = strdup(desc); - tank_info[i].ml = type->size.mliter; - tank_info[i].bar = type->workingpressure.mbar / 1000; - } -} -void add_weightsystem_description(weightsystem_t *weightsystem) -{ - const char *desc; - int i; - - desc = weightsystem->description; - if (!desc) - return; - for (i = 0; i < 100 && ws_info[i].name != NULL; i++) { - if (strcmp(ws_info[i].name, desc) == 0) { - ws_info[i].grams = weightsystem->weight.grams; - return; - } - } - if (i < 100) { - // FIXME: leaked on exit - ws_info[i].name = strdup(desc); - ws_info[i].grams = weightsystem->weight.grams; - } -} - -bool cylinder_nodata(cylinder_t *cyl) -{ - return !cyl->type.size.mliter && - !cyl->type.workingpressure.mbar && - !cyl->type.description && - !cyl->gasmix.o2.permille && - !cyl->gasmix.he.permille && - !cyl->start.mbar && - !cyl->end.mbar && - !cyl->gas_used.mliter && - !cyl->deco_gas_used.mliter; -} - -static bool cylinder_nosamples(cylinder_t *cyl) -{ - return !cyl->sample_start.mbar && - !cyl->sample_end.mbar; -} - -bool cylinder_none(void *_data) -{ - cylinder_t *cyl = _data; - return cylinder_nodata(cyl) && cylinder_nosamples(cyl); -} - -void get_gas_string(const struct gasmix *gasmix, char *text, int len) -{ - if (gasmix_is_air(gasmix)) - snprintf(text, len, "%s", translate("gettextFromC", "air")); - else if (get_he(gasmix) == 0 && get_o2(gasmix) < 1000) - snprintf(text, len, translate("gettextFromC", "EAN%d"), (get_o2(gasmix) + 5) / 10); - else if (get_he(gasmix) == 0 && get_o2(gasmix) == 1000) - snprintf(text, len, "%s", translate("gettextFromC", "oxygen")); - else - snprintf(text, len, "(%d/%d)", (get_o2(gasmix) + 5) / 10, (get_he(gasmix) + 5) / 10); -} - -/* Returns a static char buffer - only good for immediate use by printf etc */ -const char *gasname(const struct gasmix *gasmix) -{ - static char gas[64]; - get_gas_string(gasmix, gas, sizeof(gas)); - return gas; -} - -bool weightsystem_none(void *_data) -{ - weightsystem_t *ws = _data; - return !ws->weight.grams && !ws->description; -} - -bool no_weightsystems(weightsystem_t *ws) -{ - int i; - - for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) - if (!weightsystem_none(ws + i)) - return false; - return true; -} - -static bool one_weightsystem_equal(weightsystem_t *ws1, weightsystem_t *ws2) -{ - return ws1->weight.grams == ws2->weight.grams && - same_string(ws1->description, ws2->description); -} - -bool weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2) -{ - int i; - - for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) - if (!one_weightsystem_equal(ws1 + i, ws2 + i)) - return false; - return true; -} - -/* - * We hardcode the most common standard cylinders, - * we should pick up any other names from the dive - * logs directly. - */ -struct tank_info_t tank_info[100] = { - /* Need an empty entry for the no-cylinder case */ - { "", }, - - /* Size-only metric cylinders */ - { "10.0â„“", .ml = 10000 }, - { "11.1â„“", .ml = 11100 }, - - /* Most common AL cylinders */ - { "AL40", .cuft = 40, .psi = 3000 }, - { "AL50", .cuft = 50, .psi = 3000 }, - { "AL63", .cuft = 63, .psi = 3000 }, - { "AL72", .cuft = 72, .psi = 3000 }, - { "AL80", .cuft = 80, .psi = 3000 }, - { "AL100", .cuft = 100, .psi = 3300 }, - - /* Metric AL cylinders */ - { "ALU7", .ml = 7000, .bar = 200 }, - - /* Somewhat common LP steel cylinders */ - { "LP85", .cuft = 85, .psi = 2640 }, - { "LP95", .cuft = 95, .psi = 2640 }, - { "LP108", .cuft = 108, .psi = 2640 }, - { "LP121", .cuft = 121, .psi = 2640 }, - - /* Somewhat common HP steel cylinders */ - { "HP65", .cuft = 65, .psi = 3442 }, - { "HP80", .cuft = 80, .psi = 3442 }, - { "HP100", .cuft = 100, .psi = 3442 }, - { "HP119", .cuft = 119, .psi = 3442 }, - { "HP130", .cuft = 130, .psi = 3442 }, - - /* Common European steel cylinders */ - { "3â„“ 232 bar", .ml = 3000, .bar = 232 }, - { "3â„“ 300 bar", .ml = 3000, .bar = 300 }, - { "10â„“ 300 bar", .ml = 10000, .bar = 300 }, - { "12â„“ 200 bar", .ml = 12000, .bar = 200 }, - { "12â„“ 232 bar", .ml = 12000, .bar = 232 }, - { "12â„“ 300 bar", .ml = 12000, .bar = 300 }, - { "15â„“ 200 bar", .ml = 15000, .bar = 200 }, - { "15â„“ 232 bar", .ml = 15000, .bar = 232 }, - { "D7 300 bar", .ml = 14000, .bar = 300 }, - { "D8.5 232 bar", .ml = 17000, .bar = 232 }, - { "D12 232 bar", .ml = 24000, .bar = 232 }, - { "D13 232 bar", .ml = 26000, .bar = 232 }, - { "D15 232 bar", .ml = 30000, .bar = 232 }, - { "D16 232 bar", .ml = 32000, .bar = 232 }, - { "D18 232 bar", .ml = 36000, .bar = 232 }, - { "D20 232 bar", .ml = 40000, .bar = 232 }, - - /* We'll fill in more from the dive log dynamically */ - { NULL, } -}; - -/* - * We hardcode the most common weight system types - * This is a bit odd as the weight system types don't usually encode weight - */ -struct ws_info_t ws_info[100] = { - { QT_TRANSLATE_NOOP("gettextFromC", "integrated"), 0 }, - { QT_TRANSLATE_NOOP("gettextFromC", "belt"), 0 }, - { QT_TRANSLATE_NOOP("gettextFromC", "ankle"), 0 }, - { QT_TRANSLATE_NOOP("gettextFromC", "backplate weight"), 0 }, - { QT_TRANSLATE_NOOP("gettextFromC", "clip-on"), 0 }, -}; - -void remove_cylinder(struct dive *dive, int idx) -{ - cylinder_t *cyl = dive->cylinder + idx; - int nr = MAX_CYLINDERS - idx - 1; - memmove(cyl, cyl + 1, nr * sizeof(*cyl)); - memset(cyl + nr, 0, sizeof(*cyl)); -} - -void remove_weightsystem(struct dive *dive, int idx) -{ - weightsystem_t *ws = dive->weightsystem + idx; - int nr = MAX_WEIGHTSYSTEMS - idx - 1; - memmove(ws, ws + 1, nr * sizeof(*ws)); - memset(ws + nr, 0, sizeof(*ws)); -} - -/* when planning a dive we need to make sure that all cylinders have a sane depth assigned - * and if we are tracking gas consumption the pressures need to be reset to start = end = workingpressure */ -void reset_cylinders(struct dive *dive, bool track_gas) -{ - int i; - pressure_t decopo2 = {.mbar = prefs.decopo2}; - - for (i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = &dive->cylinder[i]; - if (cylinder_none(cyl)) - continue; - if (cyl->depth.mm == 0) /* if the gas doesn't give a mod, calculate based on prefs */ - cyl->depth = gas_mod(&cyl->gasmix, decopo2, dive, M_OR_FT(3,10)); - if (track_gas) - cyl->start.mbar = cyl->end.mbar = cyl->type.workingpressure.mbar; - cyl->gas_used.mliter = 0; - cyl->deco_gas_used.mliter = 0; - } -} diff --git a/subsurface-core/exif.cpp b/subsurface-core/exif.cpp deleted file mode 100644 index 0b1cda2bc..000000000 --- a/subsurface-core/exif.cpp +++ /dev/null @@ -1,587 +0,0 @@ -#include <stdio.h> -/************************************************************************** - exif.cpp -- A simple ISO C++ library to parse basic EXIF - information from a JPEG file. - - Copyright (c) 2010-2013 Mayank Lahiri - mlahiri@gmail.com - All rights reserved (BSD License). - - See exif.h for version history. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - -- Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS - OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#include <algorithm> -#include "dive.h" -#include "exif.h" - -using std::string; - -namespace { - // IF Entry - struct IFEntry { - // Raw fields - unsigned short tag; - unsigned short format; - unsigned data; - unsigned length; - - // Parsed fields - string val_string; - unsigned short val_16; - unsigned val_32; - double val_rational; - unsigned char val_byte; - }; - - // Helper functions - unsigned int parse32(const unsigned char *buf, bool intel) - { - if (intel) - return ((unsigned)buf[3] << 24) | - ((unsigned)buf[2] << 16) | - ((unsigned)buf[1] << 8) | - buf[0]; - - return ((unsigned)buf[0] << 24) | - ((unsigned)buf[1] << 16) | - ((unsigned)buf[2] << 8) | - buf[3]; - } - - unsigned short parse16(const unsigned char *buf, bool intel) - { - if (intel) - return ((unsigned)buf[1] << 8) | buf[0]; - return ((unsigned)buf[0] << 8) | buf[1]; - } - - string parseEXIFString(const unsigned char *buf, - const unsigned num_components, - const unsigned data, - const unsigned base, - const unsigned len) - { - string value; - if (num_components <= 4) - value.assign((const char *)&data, num_components); - else { - if (base + data + num_components <= len) - value.assign((const char *)(buf + base + data), num_components); - } - return value; - } - - double parseEXIFRational(const unsigned char *buf, bool intel) - { - double numerator = 0; - double denominator = 1; - - numerator = (double)parse32(buf, intel); - denominator = (double)parse32(buf + 4, intel); - if (denominator < 1e-20) - return 0; - return numerator / denominator; - } - - IFEntry parseIFEntry(const unsigned char *buf, - const unsigned offs, - const bool alignIntel, - const unsigned base, - const unsigned len) - { - IFEntry result; - - // Each directory entry is composed of: - // 2 bytes: tag number (data field) - // 2 bytes: data format - // 4 bytes: number of components - // 4 bytes: data value or offset to data value - result.tag = parse16(buf + offs, alignIntel); - result.format = parse16(buf + offs + 2, alignIntel); - result.length = parse32(buf + offs + 4, alignIntel); - result.data = parse32(buf + offs + 8, alignIntel); - - // Parse value in specified format - switch (result.format) { - case 1: - result.val_byte = (unsigned char)*(buf + offs + 8); - break; - case 2: - result.val_string = parseEXIFString(buf, result.length, result.data, base, len); - break; - case 3: - result.val_16 = parse16((const unsigned char *)buf + offs + 8, alignIntel); - break; - case 4: - result.val_32 = result.data; - break; - case 5: - if (base + result.data + 8 <= len) - result.val_rational = parseEXIFRational(buf + base + result.data, alignIntel); - break; - case 7: - case 9: - case 10: - break; - default: - result.tag = 0xFF; - } - return result; - } -} - -// -// Locates the EXIF segment and parses it using parseFromEXIFSegment -// -int EXIFInfo::parseFrom(const unsigned char *buf, unsigned len) -{ - // Sanity check: all JPEG files start with 0xFFD8 and end with 0xFFD9 - // This check also ensures that the user has supplied a correct value for len. - if (!buf || len < 4) - return PARSE_EXIF_ERROR_NO_EXIF; - if (buf[0] != 0xFF || buf[1] != 0xD8) - return PARSE_EXIF_ERROR_NO_JPEG; - if (buf[len - 2] != 0xFF || buf[len - 1] != 0xD9) - return PARSE_EXIF_ERROR_NO_JPEG; - clear(); - - // Scan for EXIF header (bytes 0xFF 0xE1) and do a sanity check by - // looking for bytes "Exif\0\0". The marker length data is in Motorola - // byte order, which results in the 'false' parameter to parse16(). - // The marker has to contain at least the TIFF header, otherwise the - // EXIF data is corrupt. So the minimum length specified here has to be: - // 2 bytes: section size - // 6 bytes: "Exif\0\0" string - // 2 bytes: TIFF header (either "II" or "MM" string) - // 2 bytes: TIFF magic (short 0x2a00 in Motorola byte order) - // 4 bytes: Offset to first IFD - // ========= - // 16 bytes - unsigned offs = 0; // current offset into buffer - for (offs = 0; offs < len - 1; offs++) - if (buf[offs] == 0xFF && buf[offs + 1] == 0xE1) - break; - if (offs + 4 > len) - return PARSE_EXIF_ERROR_NO_EXIF; - offs += 2; - unsigned short section_length = parse16(buf + offs, false); - if (offs + section_length > len || section_length < 16) - return PARSE_EXIF_ERROR_CORRUPT; - offs += 2; - - return parseFromEXIFSegment(buf + offs, len - offs); -} - -int EXIFInfo::parseFrom(const string &data) -{ - return parseFrom((const unsigned char *)data.data(), data.length()); -} - -// -// Main parsing function for an EXIF segment. -// -// PARAM: 'buf' start of the EXIF TIFF, which must be the bytes "Exif\0\0". -// PARAM: 'len' length of buffer -// -int EXIFInfo::parseFromEXIFSegment(const unsigned char *buf, unsigned len) -{ - bool alignIntel = true; // byte alignment (defined in EXIF header) - unsigned offs = 0; // current offset into buffer - if (!buf || len < 6) - return PARSE_EXIF_ERROR_NO_EXIF; - - if (!std::equal(buf, buf + 6, "Exif\0\0")) - return PARSE_EXIF_ERROR_NO_EXIF; - offs += 6; - - // Now parsing the TIFF header. The first two bytes are either "II" or - // "MM" for Intel or Motorola byte alignment. Sanity check by parsing - // the unsigned short that follows, making sure it equals 0x2a. The - // last 4 bytes are an offset into the first IFD, which are added to - // the global offset counter. For this block, we expect the following - // minimum size: - // 2 bytes: 'II' or 'MM' - // 2 bytes: 0x002a - // 4 bytes: offset to first IDF - // ----------------------------- - // 8 bytes - if (offs + 8 > len) - return PARSE_EXIF_ERROR_CORRUPT; - unsigned tiff_header_start = offs; - if (buf[offs] == 'I' && buf[offs + 1] == 'I') - alignIntel = true; - else { - if (buf[offs] == 'M' && buf[offs + 1] == 'M') - alignIntel = false; - else - return PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN; - } - this->ByteAlign = alignIntel; - offs += 2; - if (0x2a != parse16(buf + offs, alignIntel)) - return PARSE_EXIF_ERROR_CORRUPT; - offs += 2; - unsigned first_ifd_offset = parse32(buf + offs, alignIntel); - offs += first_ifd_offset - 4; - if (offs >= len) - return PARSE_EXIF_ERROR_CORRUPT; - - // Now parsing the first Image File Directory (IFD0, for the main image). - // An IFD consists of a variable number of 12-byte directory entries. The - // first two bytes of the IFD section contain the number of directory - // entries in the section. The last 4 bytes of the IFD contain an offset - // to the next IFD, which means this IFD must contain exactly 6 + 12 * num - // bytes of data. - if (offs + 2 > len) - return PARSE_EXIF_ERROR_CORRUPT; - int num_entries = parse16(buf + offs, alignIntel); - if (offs + 6 + 12 * num_entries > len) - return PARSE_EXIF_ERROR_CORRUPT; - offs += 2; - unsigned exif_sub_ifd_offset = len; - unsigned gps_sub_ifd_offset = len; - while (--num_entries >= 0) { - IFEntry result = parseIFEntry(buf, offs, alignIntel, tiff_header_start, len); - offs += 12; - switch (result.tag) { - case 0x102: - // Bits per sample - if (result.format == 3) - this->BitsPerSample = result.val_16; - break; - - case 0x10E: - // Image description - if (result.format == 2) - this->ImageDescription = result.val_string; - break; - - case 0x10F: - // Digicam make - if (result.format == 2) - this->Make = result.val_string; - break; - - case 0x110: - // Digicam model - if (result.format == 2) - this->Model = result.val_string; - break; - - case 0x112: - // Orientation of image - if (result.format == 3) - this->Orientation = result.val_16; - break; - - case 0x131: - // Software used for image - if (result.format == 2) - this->Software = result.val_string; - break; - - case 0x132: - // EXIF/TIFF date/time of image modification - if (result.format == 2) - this->DateTime = result.val_string; - break; - - case 0x8298: - // Copyright information - if (result.format == 2) - this->Copyright = result.val_string; - break; - - case 0x8825: - // GPS IFS offset - gps_sub_ifd_offset = tiff_header_start + result.data; - break; - - case 0x8769: - // EXIF SubIFD offset - exif_sub_ifd_offset = tiff_header_start + result.data; - break; - } - } - - // Jump to the EXIF SubIFD if it exists and parse all the information - // there. Note that it's possible that the EXIF SubIFD doesn't exist. - // The EXIF SubIFD contains most of the interesting information that a - // typical user might want. - if (exif_sub_ifd_offset + 4 <= len) { - offs = exif_sub_ifd_offset; - int num_entries = parse16(buf + offs, alignIntel); - if (offs + 6 + 12 * num_entries > len) - return PARSE_EXIF_ERROR_CORRUPT; - offs += 2; - while (--num_entries >= 0) { - IFEntry result = parseIFEntry(buf, offs, alignIntel, tiff_header_start, len); - switch (result.tag) { - case 0x829a: - // Exposure time in seconds - if (result.format == 5) - this->ExposureTime = result.val_rational; - break; - - case 0x829d: - // FNumber - if (result.format == 5) - this->FNumber = result.val_rational; - break; - - case 0x8827: - // ISO Speed Rating - if (result.format == 3) - this->ISOSpeedRatings = result.val_16; - break; - - case 0x9003: - // Original date and time - if (result.format == 2) - this->DateTimeOriginal = result.val_string; - break; - - case 0x9004: - // Digitization date and time - if (result.format == 2) - this->DateTimeDigitized = result.val_string; - break; - - case 0x9201: - // Shutter speed value - if (result.format == 5) - this->ShutterSpeedValue = result.val_rational; - break; - - case 0x9204: - // Exposure bias value - if (result.format == 5) - this->ExposureBiasValue = result.val_rational; - break; - - case 0x9206: - // Subject distance - if (result.format == 5) - this->SubjectDistance = result.val_rational; - break; - - case 0x9209: - // Flash used - if (result.format == 3) - this->Flash = result.data ? 1 : 0; - break; - - case 0x920a: - // Focal length - if (result.format == 5) - this->FocalLength = result.val_rational; - break; - - case 0x9207: - // Metering mode - if (result.format == 3) - this->MeteringMode = result.val_16; - break; - - case 0x9291: - // Subsecond original time - if (result.format == 2) - this->SubSecTimeOriginal = result.val_string; - break; - - case 0xa002: - // EXIF Image width - if (result.format == 4) - this->ImageWidth = result.val_32; - if (result.format == 3) - this->ImageWidth = result.val_16; - break; - - case 0xa003: - // EXIF Image height - if (result.format == 4) - this->ImageHeight = result.val_32; - if (result.format == 3) - this->ImageHeight = result.val_16; - break; - - case 0xa405: - // Focal length in 35mm film - if (result.format == 3) - this->FocalLengthIn35mm = result.val_16; - break; - } - offs += 12; - } - } - - // Jump to the GPS SubIFD if it exists and parse all the information - // there. Note that it's possible that the GPS SubIFD doesn't exist. - if (gps_sub_ifd_offset + 4 <= len) { - offs = gps_sub_ifd_offset; - int num_entries = parse16(buf + offs, alignIntel); - if (offs + 6 + 12 * num_entries > len) - return PARSE_EXIF_ERROR_CORRUPT; - offs += 2; - while (--num_entries >= 0) { - unsigned short tag = parse16(buf + offs, alignIntel); - unsigned short format = parse16(buf + offs + 2, alignIntel); - unsigned length = parse32(buf + offs + 4, alignIntel); - unsigned data = parse32(buf + offs + 8, alignIntel); - switch (tag) { - case 1: - // GPS north or south - this->GeoLocation.LatComponents.direction = *(buf + offs + 8); - if ('S' == this->GeoLocation.LatComponents.direction) - this->GeoLocation.Latitude = -this->GeoLocation.Latitude; - break; - - case 2: - // GPS latitude - if (format == 5 && length == 3) { - this->GeoLocation.LatComponents.degrees = - parseEXIFRational(buf + data + tiff_header_start, alignIntel); - this->GeoLocation.LatComponents.minutes = - parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel); - this->GeoLocation.LatComponents.seconds = - parseEXIFRational(buf + data + tiff_header_start + 16, alignIntel); - this->GeoLocation.Latitude = - this->GeoLocation.LatComponents.degrees + - this->GeoLocation.LatComponents.minutes / 60 + - this->GeoLocation.LatComponents.seconds / 3600; - if ('S' == this->GeoLocation.LatComponents.direction) - this->GeoLocation.Latitude = -this->GeoLocation.Latitude; - } - break; - - case 3: - // GPS east or west - this->GeoLocation.LonComponents.direction = *(buf + offs + 8); - if ('W' == this->GeoLocation.LonComponents.direction) - this->GeoLocation.Longitude = -this->GeoLocation.Longitude; - break; - - case 4: - // GPS longitude - if (format == 5 && length == 3) { - this->GeoLocation.LonComponents.degrees = - parseEXIFRational(buf + data + tiff_header_start, alignIntel); - this->GeoLocation.LonComponents.minutes = - parseEXIFRational(buf + data + tiff_header_start + 8, alignIntel); - this->GeoLocation.LonComponents.seconds = - parseEXIFRational(buf + data + tiff_header_start + 16, alignIntel); - this->GeoLocation.Longitude = - this->GeoLocation.LonComponents.degrees + - this->GeoLocation.LonComponents.minutes / 60 + - this->GeoLocation.LonComponents.seconds / 3600; - if ('W' == this->GeoLocation.LonComponents.direction) - this->GeoLocation.Longitude = -this->GeoLocation.Longitude; - } - break; - - case 5: - // GPS altitude reference (below or above sea level) - this->GeoLocation.AltitudeRef = *(buf + offs + 8); - if (1 == this->GeoLocation.AltitudeRef) - this->GeoLocation.Altitude = -this->GeoLocation.Altitude; - break; - - case 6: - // GPS altitude reference - if (format == 5) { - this->GeoLocation.Altitude = - parseEXIFRational(buf + data + tiff_header_start, alignIntel); - if (1 == this->GeoLocation.AltitudeRef) - this->GeoLocation.Altitude = -this->GeoLocation.Altitude; - } - break; - } - offs += 12; - } - } - - return PARSE_EXIF_SUCCESS; -} - -void EXIFInfo::clear() -{ - // Strings - ImageDescription.clear(); - Make.clear(); - Model.clear(); - Software.clear(); - DateTime.clear(); - DateTimeOriginal.clear(); - DateTimeDigitized.clear(); - SubSecTimeOriginal.clear(); - Copyright.clear(); - - // Shorts / unsigned / double - ByteAlign = 0; - Orientation = 0; - - BitsPerSample = 0; - ExposureTime = 0; - FNumber = 0; - ISOSpeedRatings = 0; - ShutterSpeedValue = 0; - ExposureBiasValue = 0; - SubjectDistance = 0; - FocalLength = 0; - FocalLengthIn35mm = 0; - Flash = 0; - MeteringMode = 0; - ImageWidth = 0; - ImageHeight = 0; - - // Geolocation - GeoLocation.Latitude = 0; - GeoLocation.Longitude = 0; - GeoLocation.Altitude = 0; - GeoLocation.AltitudeRef = 0; - GeoLocation.LatComponents.degrees = 0; - GeoLocation.LatComponents.minutes = 0; - GeoLocation.LatComponents.seconds = 0; - GeoLocation.LatComponents.direction = 0; - GeoLocation.LonComponents.degrees = 0; - GeoLocation.LonComponents.minutes = 0; - GeoLocation.LonComponents.seconds = 0; - GeoLocation.LonComponents.direction = 0; -} - -time_t EXIFInfo::epoch() -{ - struct tm tm; - int year, month, day, hour, min, sec; - - if (DateTimeOriginal.size()) - sscanf(DateTimeOriginal.c_str(), "%d:%d:%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec); - else - sscanf(DateTime.c_str(), "%d:%d:%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec); - tm.tm_year = year; - tm.tm_mon = month - 1; - tm.tm_mday = day; - tm.tm_hour = hour; - tm.tm_min = min; - tm.tm_sec = sec; - return (utc_mktime(&tm)); -} diff --git a/subsurface-core/exif.h b/subsurface-core/exif.h deleted file mode 100644 index 0fb3a7d4a..000000000 --- a/subsurface-core/exif.h +++ /dev/null @@ -1,147 +0,0 @@ -/************************************************************************** - exif.h -- A simple ISO C++ library to parse basic EXIF - information from a JPEG file. - - Based on the description of the EXIF file format at: - -- http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html - -- http://www.media.mit.edu/pia/Research/deepview/exif.html - -- http://www.exif.org/Exif2-2.PDF - - Copyright (c) 2010-2013 Mayank Lahiri - mlahiri@gmail.com - All rights reserved. - - VERSION HISTORY: - ================ - - 2.1: Released July 2013 - -- fixed a bug where JPEGs without an EXIF SubIFD would not be parsed - -- fixed a bug in parsing GPS coordinate seconds - -- fixed makefile bug - -- added two pathological test images from Matt Galloway - http://www.galloway.me.uk/2012/01/uiimageorientation-exif-orientation-sample-images/ - -- split main parsing routine for easier integration into Firefox - - 2.0: Released February 2013 - -- complete rewrite - -- no new/delete - -- added GPS support - - 1.0: Released 2010 - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - -- Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS - OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#ifndef EXIF_H -#define EXIF_H - -#include <string> - -// -// Class responsible for storing and parsing EXIF information from a JPEG blob -// -class EXIFInfo { -public: - // Parsing function for an entire JPEG image buffer. - // - // PARAM 'data': A pointer to a JPEG image. - // PARAM 'length': The length of the JPEG image. - // RETURN: PARSE_EXIF_SUCCESS (0) on succes with 'result' filled out - // error code otherwise, as defined by the PARSE_EXIF_ERROR_* macros - int parseFrom(const unsigned char *data, unsigned length); - int parseFrom(const std::string &data); - - // Parsing function for an EXIF segment. This is used internally by parseFrom() - // but can be called for special cases where only the EXIF section is - // available (i.e., a blob starting with the bytes "Exif\0\0"). - int parseFromEXIFSegment(const unsigned char *buf, unsigned len); - - // Set all data members to default values. - void clear(); - - // Data fields filled out by parseFrom() - char ByteAlign; // 0 = Motorola byte alignment, 1 = Intel - std::string ImageDescription; // Image description - std::string Make; // Camera manufacturer's name - std::string Model; // Camera model - unsigned short Orientation; // Image orientation, start of data corresponds to - // 0: unspecified in EXIF data - // 1: upper left of image - // 3: lower right of image - // 6: upper right of image - // 8: lower left of image - // 9: undefined - unsigned short BitsPerSample; // Number of bits per component - std::string Software; // Software used - std::string DateTime; // File change date and time - std::string DateTimeOriginal; // Original file date and time (may not exist) - std::string DateTimeDigitized; // Digitization date and time (may not exist) - std::string SubSecTimeOriginal; // Sub-second time that original picture was taken - std::string Copyright; // File copyright information - double ExposureTime; // Exposure time in seconds - double FNumber; // F/stop - unsigned short ISOSpeedRatings; // ISO speed - double ShutterSpeedValue; // Shutter speed (reciprocal of exposure time) - double ExposureBiasValue; // Exposure bias value in EV - double SubjectDistance; // Distance to focus point in meters - double FocalLength; // Focal length of lens in millimeters - unsigned short FocalLengthIn35mm; // Focal length in 35mm film - char Flash; // 0 = no flash, 1 = flash used - unsigned short MeteringMode; // Metering mode - // 1: average - // 2: center weighted average - // 3: spot - // 4: multi-spot - // 5: multi-segment - unsigned ImageWidth; // Image width reported in EXIF data - unsigned ImageHeight; // Image height reported in EXIF data - struct Geolocation_t - { // GPS information embedded in file - double Latitude; // Image latitude expressed as decimal - double Longitude; // Image longitude expressed as decimal - double Altitude; // Altitude in meters, relative to sea level - char AltitudeRef; // 0 = above sea level, -1 = below sea level - struct Coord_t { - double degrees; - double minutes; - double seconds; - char direction; - } LatComponents, LonComponents; // Latitude, Longitude expressed in deg/min/sec - } GeoLocation; - EXIFInfo() - { - clear(); - } - - time_t epoch(); -}; - -// Parse was successful -#define PARSE_EXIF_SUCCESS 0 -// No JPEG markers found in buffer, possibly invalid JPEG file -#define PARSE_EXIF_ERROR_NO_JPEG 1982 -// No EXIF header found in JPEG file. -#define PARSE_EXIF_ERROR_NO_EXIF 1983 -// Byte alignment specified in EXIF file was unknown (not Motorola or Intel). -#define PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN 1984 -// EXIF header was found, but data was corrupted. -#define PARSE_EXIF_ERROR_CORRUPT 1985 - -#endif // EXIF_H diff --git a/subsurface-core/file.c b/subsurface-core/file.c deleted file mode 100644 index 1337da3a2..000000000 --- a/subsurface-core/file.c +++ /dev/null @@ -1,1115 +0,0 @@ -#include <unistd.h> -#include <fcntl.h> -#include <sys/stat.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include "gettext.h" -#include <zip.h> -#include <time.h> - -#include "dive.h" -#include "divelist.h" -#include "file.h" -#include "git-access.h" -#include "qthelperfromc.h" - -/* For SAMPLE_* */ -#include <libdivecomputer/parser.h> - -/* to check XSLT version number */ -#include <libxslt/xsltconfig.h> - -/* Crazy windows sh*t */ -#ifndef O_BINARY -#define O_BINARY 0 -#endif - -int readfile(const char *filename, struct memblock *mem) -{ - int ret, fd; - struct stat st; - char *buf; - - mem->buffer = NULL; - mem->size = 0; - - fd = subsurface_open(filename, O_RDONLY | O_BINARY, 0); - if (fd < 0) - return fd; - ret = fstat(fd, &st); - if (ret < 0) - goto out; - ret = -EINVAL; - if (!S_ISREG(st.st_mode)) - goto out; - ret = 0; - if (!st.st_size) - goto out; - buf = malloc(st.st_size + 1); - ret = -1; - errno = ENOMEM; - if (!buf) - goto out; - mem->buffer = buf; - mem->size = st.st_size; - ret = read(fd, buf, mem->size); - if (ret < 0) - goto free; - buf[ret] = 0; - if (ret == (int)mem->size) // converting to int loses a bit but size will never be that big - goto out; - errno = EIO; - ret = -1; -free: - free(mem->buffer); - mem->buffer = NULL; - mem->size = 0; -out: - close(fd); - return ret; -} - - -static void zip_read(struct zip_file *file, const char *filename) -{ - int size = 1024, n, read = 0; - char *mem = malloc(size); - - while ((n = zip_fread(file, mem + read, size - read)) > 0) { - read += n; - size = read * 3 / 2; - mem = realloc(mem, size); - } - mem[read] = 0; - (void) parse_xml_buffer(filename, mem, read, &dive_table, NULL); - free(mem); -} - -int try_to_open_zip(const char *filename) -{ - int success = 0; - /* Grr. libzip needs to re-open the file, it can't take a buffer */ - struct zip *zip = subsurface_zip_open_readonly(filename, ZIP_CHECKCONS, NULL); - - if (zip) { - int index; - for (index = 0;; index++) { - struct zip_file *file = zip_fopen_index(zip, index, 0); - if (!file) - break; - /* skip parsing the divelogs.de pictures */ - if (strstr(zip_get_name(zip, index, 0), "pictures/")) - continue; - zip_read(file, filename); - zip_fclose(file); - success++; - } - subsurface_zip_close(zip); - - if (!success) - return report_error(translate("gettextFromC", "No dives in the input file '%s'"), filename); - } - return success; -} - -static int try_to_xslt_open_csv(const char *filename, struct memblock *mem, const char *tag) -{ - char *buf; - - if (mem->size == 0 && readfile(filename, mem) < 0) - return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); - - /* Surround the CSV file content with XML tags to enable XSLT - * parsing - * - * Tag markers take: strlen("<></>") = 5 - */ - buf = realloc(mem->buffer, mem->size + 7 + strlen(tag) * 2); - if (buf != NULL) { - char *starttag = NULL; - char *endtag = NULL; - - starttag = malloc(3 + strlen(tag)); - endtag = malloc(5 + strlen(tag)); - - if (starttag == NULL || endtag == NULL) { - /* this is fairly silly - so the malloc fails, but we strdup the error? - * let's complete the silliness by freeing the two pointers in case one malloc succeeded - * and the other one failed - this will make static analysis tools happy */ - free(starttag); - free(endtag); - free(buf); - return report_error("Memory allocation failed in %s", __func__); - } - - sprintf(starttag, "<%s>", tag); - sprintf(endtag, "\n</%s>", tag); - - memmove(buf + 2 + strlen(tag), buf, mem->size); - memcpy(buf, starttag, 2 + strlen(tag)); - memcpy(buf + mem->size + 2 + strlen(tag), endtag, 5 + strlen(tag)); - mem->size += (6 + 2 * strlen(tag)); - mem->buffer = buf; - - free(starttag); - free(endtag); - } else { - free(mem->buffer); - return report_error("realloc failed in %s", __func__); - } - - return 0; -} - -int db_test_func(void *param, int columns, char **data, char **column) -{ - (void) param; - (void) columns; - (void) column; - return *data[0] == '0'; -} - - -static int try_to_open_db(const char *filename, struct memblock *mem) -{ - sqlite3 *handle; - char dm4_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%ProfileBlob%'"; - char dm5_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%SampleBlob%'"; - char shearwater_test[] = "select count(*) from sqlite_master where type='table' and name='system' and sql like '%dbVersion%'"; - char cobalt_test[] = "select count(*) from sqlite_master where type='table' and name='TrackPoints' and sql like '%DepthPressure%'"; - char divinglog_test[] = "select count(*) from sqlite_master where type='table' and name='DBInfo' and sql like '%PrgName%'"; - int retval; - - retval = sqlite3_open(filename, &handle); - - if (retval) { - fprintf(stderr, "Database connection failed '%s'.\n", filename); - return 1; - } - - /* Testing if DB schema resembles Suunto DM5 database format */ - retval = sqlite3_exec(handle, dm5_test, &db_test_func, 0, NULL); - if (!retval) { - retval = parse_dm5_buffer(handle, filename, mem->buffer, mem->size, &dive_table); - sqlite3_close(handle); - return retval; - } - - /* Testing if DB schema resembles Suunto DM4 database format */ - retval = sqlite3_exec(handle, dm4_test, &db_test_func, 0, NULL); - if (!retval) { - retval = parse_dm4_buffer(handle, filename, mem->buffer, mem->size, &dive_table); - sqlite3_close(handle); - return retval; - } - - /* Testing if DB schema resembles Shearwater database format */ - retval = sqlite3_exec(handle, shearwater_test, &db_test_func, 0, NULL); - if (!retval) { - retval = parse_shearwater_buffer(handle, filename, mem->buffer, mem->size, &dive_table); - sqlite3_close(handle); - return retval; - } - - /* Testing if DB schema resembles Atomic Cobalt database format */ - retval = sqlite3_exec(handle, cobalt_test, &db_test_func, 0, NULL); - if (!retval) { - retval = parse_cobalt_buffer(handle, filename, mem->buffer, mem->size, &dive_table); - sqlite3_close(handle); - return retval; - } - - /* Testing if DB schema resembles Divinglog database format */ - retval = sqlite3_exec(handle, divinglog_test, &db_test_func, 0, NULL); - if (!retval) { - retval = parse_divinglog_buffer(handle, filename, mem->buffer, mem->size, &dive_table); - sqlite3_close(handle); - return retval; - } - - sqlite3_close(handle); - - return retval; -} - -timestamp_t parse_date(const char *date) -{ - int hour, min, sec; - struct tm tm; - char *p; - - memset(&tm, 0, sizeof(tm)); - tm.tm_mday = strtol(date, &p, 10); - if (tm.tm_mday < 1 || tm.tm_mday > 31) - return 0; - for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) { - if (!memcmp(p, monthname(tm.tm_mon), 3)) - break; - } - if (tm.tm_mon > 11) - return 0; - date = p + 3; - tm.tm_year = strtol(date, &p, 10); - if (date == p) - return 0; - if (tm.tm_year < 70) - tm.tm_year += 2000; - if (tm.tm_year < 100) - tm.tm_year += 1900; - if (sscanf(p, "%d:%d:%d", &hour, &min, &sec) != 3) - return 0; - tm.tm_hour = hour; - tm.tm_min = min; - tm.tm_sec = sec; - return utc_mktime(&tm); -} - -enum csv_format { - CSV_DEPTH, - CSV_TEMP, - CSV_PRESSURE, - POSEIDON_DEPTH, - POSEIDON_TEMP, - POSEIDON_SETPOINT, - POSEIDON_SENSOR1, - POSEIDON_SENSOR2, - POSEIDON_PRESSURE, - POSEIDON_O2CYLINDER, - POSEIDON_NDL, - POSEIDON_CEILING -}; - -static void add_sample_data(struct sample *sample, enum csv_format type, double val) -{ - switch (type) { - case CSV_DEPTH: - sample->depth.mm = feet_to_mm(val); - break; - case CSV_TEMP: - sample->temperature.mkelvin = F_to_mkelvin(val); - break; - case CSV_PRESSURE: - sample->cylinderpressure.mbar = psi_to_mbar(val * 4); - break; - case POSEIDON_DEPTH: - sample->depth.mm = val * 0.5 *1000; - break; - case POSEIDON_TEMP: - sample->temperature.mkelvin = C_to_mkelvin(val * 0.2); - break; - case POSEIDON_SETPOINT: - sample->setpoint.mbar = val * 10; - break; - case POSEIDON_SENSOR1: - sample->o2sensor[0].mbar = val * 10; - break; - case POSEIDON_SENSOR2: - sample->o2sensor[1].mbar = val * 10; - break; - case POSEIDON_PRESSURE: - sample->cylinderpressure.mbar = val * 1000; - break; - case POSEIDON_O2CYLINDER: - sample->o2cylinderpressure.mbar = val * 1000; - break; - case POSEIDON_NDL: - sample->ndl.seconds = val * 60; - break; - case POSEIDON_CEILING: - sample->stopdepth.mm = val * 1000; - break; - } -} - -/* - * Cochran comma-separated values: depth in feet, temperature in F, pressure in psi. - * - * They start with eight comma-separated fields like: - * - * filename: {C:\Analyst4\can\T036785.can},{C:\Analyst4\can\K031892.can} - * divenr: %d - * datetime: {03Sep11 16:37:22},{15Dec11 18:27:02} - * ??: 1 - * serialnr??: {CCI134},{CCI207} - * computer??: {GeminiII},{CommanderIII} - * computer??: {GeminiII},{CommanderIII} - * ??: 1 - * - * Followed by the data values (all comma-separated, all one long line). - */ -static int try_to_open_csv(struct memblock *mem, enum csv_format type) -{ - char *p = mem->buffer; - char *header[8]; - int i, time; - timestamp_t date; - struct dive *dive; - struct divecomputer *dc; - - for (i = 0; i < 8; i++) { - header[i] = p; - p = strchr(p, ','); - if (!p) - return 0; - p++; - } - - date = parse_date(header[2]); - if (!date) - return 0; - - dive = alloc_dive(); - dive->when = date; - dive->number = atoi(header[1]); - dc = &dive->dc; - - time = 0; - for (;;) { - char *end; - double val; - struct sample *sample; - - errno = 0; - val = strtod(p, &end); // FIXME == localization issue - if (end == p) - break; - if (errno) - break; - - sample = prepare_sample(dc); - sample->time.seconds = time; - add_sample_data(sample, type, val); - finish_sample(dc); - - time++; - dc->duration.seconds = time; - if (*end != ',') - break; - p = end + 1; - } - record_dive(dive); - return 1; -} - -static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem) -{ - // hack to be able to provide a comment for the translated string - static char *csv_warning = QT_TRANSLATE_NOOP3("gettextFromC", - "Cannot open CSV file %s; please use Import log file dialog", - "'Import log file' should be the same text as corresponding label in Import menu"); - - /* Suunto Dive Manager files: SDE, ZIP; divelogs.de files: DLD */ - if (!strcasecmp(fmt, "SDE") || !strcasecmp(fmt, "ZIP") || !strcasecmp(fmt, "DLD")) - return try_to_open_zip(filename); - - /* CSV files */ - if (!strcasecmp(fmt, "CSV")) - return report_error(translate("gettextFromC", csv_warning), filename); - /* Truly nasty intentionally obfuscated Cochran Anal software */ - if (!strcasecmp(fmt, "CAN")) - return try_to_open_cochran(filename, mem); - /* Cochran export comma-separated-value files */ - if (!strcasecmp(fmt, "DPT")) - return try_to_open_csv(mem, CSV_DEPTH); - if (!strcasecmp(fmt, "LVD")) - return try_to_open_liquivision(filename, mem); - if (!strcasecmp(fmt, "TMP")) - return try_to_open_csv(mem, CSV_TEMP); - if (!strcasecmp(fmt, "HP1")) - return try_to_open_csv(mem, CSV_PRESSURE); - - return 0; -} - -static int parse_file_buffer(const char *filename, struct memblock *mem) -{ - int ret; - char *fmt = strrchr(filename, '.'); - if (fmt && (ret = open_by_filename(filename, fmt + 1, mem)) != 0) - return ret; - - if (!mem->size || !mem->buffer) - return report_error("Out of memory parsing file %s\n", filename); - - return parse_xml_buffer(filename, mem->buffer, mem->size, &dive_table, NULL); -} - -int check_git_sha(const char *filename) -{ - struct git_repository *git; - const char *branch = NULL; - - git = is_git_repository(filename, &branch, NULL, false); - if (prefs.cloud_git_url && - strstr(filename, prefs.cloud_git_url) - && git == dummy_git_repository) - /* opening the cloud storage repository failed for some reason, - * so we don't know if there is additional data in the remote */ - return 1; - - /* if this is a git repository, do we already have this exact state loaded ? - * get the SHA and compare with what we currently have */ - if (git && git != dummy_git_repository) { - const char *sha = get_sha(git, branch); - if (!same_string(sha, "") && - same_string(sha, saved_git_id)) { - fprintf(stderr, "already have loaded SHA %s - don't load again\n", sha); - return 0; - } - } - return 1; -} - -int parse_file(const char *filename) -{ - struct git_repository *git; - const char *branch = NULL; - char *current_sha = copy_string(saved_git_id); - struct memblock mem; - char *fmt; - int ret; - - git = is_git_repository(filename, &branch, NULL, false); - if (prefs.cloud_git_url && - strstr(filename, prefs.cloud_git_url) - && git == dummy_git_repository) { - /* opening the cloud storage repository failed for some reason - * give up here and don't send errors about git repositories */ - free(current_sha); - return 0; - } - /* if this is a git repository, do we already have this exact state loaded ? - * get the SHA and compare with what we currently have */ - if (git && git != dummy_git_repository) { - const char *sha = get_sha(git, branch); - if (!same_string(sha, "") && - same_string(sha, current_sha) && - !unsaved_changes()) { - fprintf(stderr, "already have loaded SHA %s - don't load again\n", sha); - free(current_sha); - return 0; - } - } - free(current_sha); - if (git) - return git_load_dives(git, branch); - - if ((ret = readfile(filename, &mem)) < 0) { - /* we don't want to display an error if this was the default file or the cloud storage */ - if ((prefs.default_filename && !strcmp(filename, prefs.default_filename)) || - isCloudUrl(filename)) - return 0; - - return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); - } else if (ret == 0) { - return report_error(translate("gettextFromC", "Empty file '%s'"), filename); - } - - fmt = strrchr(filename, '.'); - if (fmt && (!strcasecmp(fmt + 1, "DB") || !strcasecmp(fmt + 1, "BAK") || !strcasecmp(fmt + 1, "SQL"))) { - if (!try_to_open_db(filename, &mem)) { - free(mem.buffer); - return 0; - } - } - - /* Divesoft Freedom */ - if (fmt && (!strcasecmp(fmt + 1, "DLF"))) { - if (!parse_dlf_buffer(mem.buffer, mem.size)) { - free(mem.buffer); - return 0; - } - return -1; - } - - /* DataTrak/Wlog */ - if (fmt && !strcasecmp(fmt + 1, "LOG")) { - datatrak_import(filename, &dive_table); - return 0; - } - - /* OSTCtools */ - if (fmt && (!strcasecmp(fmt + 1, "DIVE"))) { - ostctools_import(filename, &dive_table); - return 0; - } - - ret = parse_file_buffer(filename, &mem); - free(mem.buffer); - return ret; -} - -#define MATCH(buffer, pattern) \ - memcmp(buffer, pattern, strlen(pattern)) - -char *parse_mkvi_value(const char *haystack, const char *needle) -{ - char *lineptr, *valueptr, *endptr, *ret = NULL; - - if ((lineptr = strstr(haystack, needle)) != NULL) { - if ((valueptr = strstr(lineptr, ": ")) != NULL) { - valueptr += 2; - } - if ((endptr = strstr(lineptr, "\n")) != NULL) { - char terminator = '\n'; - if (*(endptr - 1) == '\r') { - --endptr; - terminator = '\r'; - } - *endptr = 0; - ret = copy_string(valueptr); - *endptr = terminator; - - } - } - return ret; -} - -char *next_mkvi_key(const char *haystack) -{ - char *valueptr, *endptr, *ret = NULL; - - if ((valueptr = strstr(haystack, "\n")) != NULL) { - valueptr += 1; - if ((endptr = strstr(valueptr, ": ")) != NULL) { - *endptr = 0; - ret = strdup(valueptr); - *endptr = ':'; - } - } - return ret; -} - -int parse_txt_file(const char *filename, const char *csv) -{ - struct memblock memtxt, memcsv; - - if (readfile(filename, &memtxt) < 0) { - return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); - } - - /* - * MkVI stores some information in .txt file but the whole profile and events are stored in .csv file. First - * make sure the input .txt looks like proper MkVI file, then start parsing the .csv. - */ - if (MATCH(memtxt.buffer, "MkVI_Config") == 0) { - int d, m, y, he; - int hh = 0, mm = 0, ss = 0; - int prev_depth = 0, cur_sampletime = 0, prev_setpoint = -1, prev_ndl = -1; - bool has_depth = false, has_setpoint = false, has_ndl = false; - char *lineptr, *key, *value; - int o2cylinder_pressure = 0, cylinder_pressure = 0, cur_cylinder_index = 0; - unsigned int prev_time = 0; - - struct dive *dive; - struct divecomputer *dc; - struct tm cur_tm; - - value = parse_mkvi_value(memtxt.buffer, "Dive started at"); - if (sscanf(value, "%d-%d-%d %d:%d:%d", &y, &m, &d, &hh, &mm, &ss) != 6) { - free(value); - return -1; - } - free(value); - cur_tm.tm_year = y; - cur_tm.tm_mon = m - 1; - cur_tm.tm_mday = d; - cur_tm.tm_hour = hh; - cur_tm.tm_min = mm; - cur_tm.tm_sec = ss; - - dive = alloc_dive(); - dive->when = utc_mktime(&cur_tm);; - dive->dc.model = strdup("Poseidon MkVI Discovery"); - value = parse_mkvi_value(memtxt.buffer, "Rig Serial number"); - dive->dc.deviceid = atoi(value); - free(value); - dive->dc.divemode = CCR; - dive->dc.no_o2sensors = 2; - - dive->cylinder[cur_cylinder_index].cylinder_use = OXYGEN; - dive->cylinder[cur_cylinder_index].type.size.mliter = 3000; - dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000; - dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6"); - dive->cylinder[cur_cylinder_index].gasmix.o2.permille = 1000; - cur_cylinder_index++; - - dive->cylinder[cur_cylinder_index].cylinder_use = DILUENT; - dive->cylinder[cur_cylinder_index].type.size.mliter = 3000; - dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000; - dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6"); - value = parse_mkvi_value(memtxt.buffer, "Helium percentage"); - he = atoi(value); - free(value); - value = parse_mkvi_value(memtxt.buffer, "Nitrogen percentage"); - dive->cylinder[cur_cylinder_index].gasmix.o2.permille = (100 - atoi(value) - he) * 10; - free(value); - dive->cylinder[cur_cylinder_index].gasmix.he.permille = he * 10; - cur_cylinder_index++; - - lineptr = strstr(memtxt.buffer, "Dive started at"); - while (lineptr && *lineptr && (lineptr = strchr(lineptr, '\n')) && ++lineptr) { - key = next_mkvi_key(lineptr); - if (!key) - break; - value = parse_mkvi_value(lineptr, key); - if (!value) { - free(key); - break; - } - add_extra_data(&dive->dc, key, value); - free(key); - free(value); - } - dc = &dive->dc; - - /* - * Read samples from the CSV file. A sample contains all the lines with same timestamp. The CSV file has - * the following format: - * - * timestamp, type, value - * - * And following fields are of interest to us: - * - * 6 sensor1 - * 7 sensor2 - * 8 depth - * 13 o2 tank pressure - * 14 diluent tank pressure - * 20 o2 setpoint - * 39 water temp - */ - - if (readfile(csv, &memcsv) < 0) { - free(dive); - return report_error(translate("gettextFromC", "Poseidon import failed: unable to read '%s'"), csv); - } - lineptr = memcsv.buffer; - for (;;) { - struct sample *sample; - int type; - int value; - int sampletime; - int gaschange = 0; - - /* Collect all the information for one sample */ - sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value); - - has_depth = false; - has_setpoint = false; - has_ndl = false; - sample = prepare_sample(dc); - - /* - * There was a bug in MKVI download tool that resulted in erroneous sample - * times. This fix should work similarly as the vendor's own. - */ - - sample->time.seconds = cur_sampletime < 0xFFFF * 3 / 4 ? cur_sampletime : prev_time; - prev_time = sample->time.seconds; - - do { - int i = sscanf(lineptr, "%d,%d,%d", &sampletime, &type, &value); - switch (i) { - case 3: - switch (type) { - case 0: - //Mouth piece position event: 0=OC, 1=CC, 2=UN, 3=NC - switch (value) { - case 0: - add_event(dc, cur_sampletime, 0, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position OC")); - break; - case 1: - add_event(dc, cur_sampletime, 0, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position CC")); - break; - case 2: - add_event(dc, cur_sampletime, 0, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position unknown")); - break; - case 3: - add_event(dc, cur_sampletime, 0, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position not connected")); - break; - } - break; - case 3: - //Power Off event - add_event(dc, cur_sampletime, 0, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Power off")); - break; - case 4: - //Battery State of Charge in % -#ifdef SAMPLE_EVENT_BATTERY - add_event(dc, cur_sampletime, SAMPLE_EVENT_BATTERY, 0, - value, QT_TRANSLATE_NOOP("gettextFromC", "battery")); -#endif - break; - case 6: - //PO2 Cell 1 Average - add_sample_data(sample, POSEIDON_SENSOR1, value); - break; - case 7: - //PO2 Cell 2 Average - add_sample_data(sample, POSEIDON_SENSOR2, value); - break; - case 8: - //Depth * 2 - has_depth = true; - prev_depth = value; - add_sample_data(sample, POSEIDON_DEPTH, value); - break; - //9 Max Depth * 2 - //10 Ascent/Descent Rate * 2 - case 11: - //Ascent Rate Alert >10 m/s - add_event(dc, cur_sampletime, SAMPLE_EVENT_ASCENT, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "ascent")); - break; - case 13: - //O2 Tank Pressure - add_sample_data(sample, POSEIDON_O2CYLINDER, value); - if (!o2cylinder_pressure) { - dive->cylinder[0].sample_start.mbar = value * 1000; - o2cylinder_pressure = value; - } else - o2cylinder_pressure = value; - break; - case 14: - //Diluent Tank Pressure - add_sample_data(sample, POSEIDON_PRESSURE, value); - if (!cylinder_pressure) { - dive->cylinder[1].sample_start.mbar = value * 1000; - cylinder_pressure = value; - } else - cylinder_pressure = value; - break; - //16 Remaining dive time #1? - //17 related to O2 injection - case 20: - //PO2 Setpoint - has_setpoint = true; - prev_setpoint = value; - add_sample_data(sample, POSEIDON_SETPOINT, value); - break; - case 22: - //End of O2 calibration Event: 0 = OK, 2 = Failed, rest of dive setpoint 1.0 - if (value == 2) - add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Oâ‚‚ calibration failed")); - add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Oâ‚‚ calibration")); - break; - case 25: - //25 Max Ascent depth - add_sample_data(sample, POSEIDON_CEILING, value); - break; - case 31: - //Start of O2 calibration Event - add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Oâ‚‚ calibration")); - break; - case 37: - //Remaining dive time #2? - has_ndl = true; - prev_ndl = value; - add_sample_data(sample, POSEIDON_NDL, value); - break; - case 39: - // Water Temperature in Celcius - add_sample_data(sample, POSEIDON_TEMP, value); - break; - case 85: - //He diluent part in % - gaschange += value << 16; - break; - case 86: - //O2 diluent part in % - gaschange += value; - break; - //239 Unknown, maybe PO2 at sensor validation? - //240 Unknown, maybe PO2 at sensor validation? - //247 Unknown, maybe PO2 Cell 1 during pressure test - //248 Unknown, maybe PO2 Cell 2 during pressure test - //250 PO2 Cell 1 - //251 PO2 Cell 2 - default: - break; - } /* sample types */ - break; - case EOF: - break; - default: - printf("Unable to parse input: %s\n", lineptr); - break; - } - - lineptr = strchr(lineptr, '\n'); - if (!lineptr || !*lineptr) - break; - lineptr++; - - /* Grabbing next sample time */ - sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value); - } while (sampletime == cur_sampletime); - - if (gaschange) - add_event(dc, cur_sampletime, SAMPLE_EVENT_GASCHANGE2, 0, gaschange, - QT_TRANSLATE_NOOP("gettextFromC", "gaschange")); - if (!has_depth) - add_sample_data(sample, POSEIDON_DEPTH, prev_depth); - if (!has_setpoint && prev_setpoint >= 0) - add_sample_data(sample, POSEIDON_SETPOINT, prev_setpoint); - if (!has_ndl && prev_ndl >= 0) - add_sample_data(sample, POSEIDON_NDL, prev_ndl); - if (cylinder_pressure) - dive->cylinder[1].sample_end.mbar = cylinder_pressure * 1000; - if (o2cylinder_pressure) - dive->cylinder[0].sample_end.mbar = o2cylinder_pressure * 1000; - finish_sample(dc); - - if (!lineptr || !*lineptr) - break; - } - record_dive(dive); - return 1; - } else { - return report_error(translate("gettextFromC", "No matching DC found for file '%s'"), csv); - } - - return 0; -} - -#define MAXCOLDIGITS 10 -#define DATESTR 9 -#define TIMESTR 6 - -int parse_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate) -{ - int ret, i; - struct memblock mem; - time_t now; - struct tm *timep = NULL; - char tmpbuf[MAXCOLDIGITS]; - - /* Increase the limits for recursion and variables on XSLT - * parsing */ - xsltMaxDepth = 30000; -#if LIBXSLT_VERSION > 10126 - xsltMaxVars = 150000; -#endif - - if (filename == NULL) - return report_error("No CSV filename"); - - time(&now); - timep = localtime(&now); - - strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep); - params[pnr++] = "date"; - params[pnr++] = strdup(tmpbuf); - - /* As the parameter is numeric, we need to ensure that the leading zero - * is not discarded during the transform, thus prepend time with 1 */ - - strftime(tmpbuf, MAXCOLDIGITS, "1%H%M", timep); - params[pnr++] = "time"; - params[pnr++] = strdup(tmpbuf); - params[pnr++] = NULL; - - mem.size = 0; - if (try_to_xslt_open_csv(filename, &mem, csvtemplate)) - return -1; - - /* - * Lets print command line for manual testing with xsltproc if - * verbosity level is high enough. The printed line needs the - * input file added as last parameter. - */ - -#ifndef SUBSURFACE_MOBILE - if (verbose >= 2) { - fprintf(stderr, "(echo '<csv>'; cat %s;echo '</csv>') | xsltproc ", filename); - for (i=0; params[i]; i+=2) - fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]); - fprintf(stderr, "%s/xslt/csv2xml.xslt -\n", SUBSURFACE_SOURCE); - } -#endif - ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params); - - free(mem.buffer); - for (i = 0; params[i]; i += 2) - free(params[i + 1]); - - return ret; -} - -#define SBPARAMS 40 -int parse_seabear_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate) -{ - int ret, i; - struct memblock mem; - time_t now; - struct tm *timep = NULL; - char *ptr, *ptr_old = NULL; - char *NL = NULL; - char tmpbuf[MAXCOLDIGITS]; - - /* Increase the limits for recursion and variables on XSLT - * parsing */ - xsltMaxDepth = 30000; -#if LIBXSLT_VERSION > 10126 - xsltMaxVars = 150000; -#endif - - time(&now); - timep = localtime(&now); - - strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep); - params[pnr++] = "date"; - params[pnr++] = strdup(tmpbuf); - - /* As the parameter is numeric, we need to ensure that the leading zero - * is not discarded during the transform, thus prepend time with 1 */ - strftime(tmpbuf, MAXCOLDIGITS, "1%H%M", timep); - params[pnr++] = "time"; - params[pnr++] = strdup(tmpbuf); - - - if (filename == NULL) - return report_error("No CSV filename"); - - if (readfile(filename, &mem) < 0) - return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); - - /* Determine NL (new line) character and the start of CSV data */ - ptr = mem.buffer; - while ((ptr = strstr(ptr, "\r\n\r\n")) != NULL) { - ptr_old = ptr; - ptr += 1; - NL = "\r\n"; - } - - if (!ptr_old) { - ptr = mem.buffer; - while ((ptr = strstr(ptr, "\n\n")) != NULL) { - ptr_old = ptr; - ptr += 1; - NL = "\n"; - } - ptr_old += 2; - } else - ptr_old += 4; - - /* - * If file does not contain empty lines, it is not a valid - * Seabear CSV file. - */ - if (NL == NULL) - return -1; - - /* - * On my current sample of Seabear DC log file, the date is - * without any identifier. Thus we must search for the previous - * line and step through from there. That is the line after - * Serial number. - */ - ptr = strstr(mem.buffer, "Serial number:"); - if (ptr) - ptr = strstr(ptr, NL); - - /* - * Write date and time values to params array, if available in - * the CSV header - */ - - if (ptr) { - ptr += strlen(NL) + 2; - /* - * pnr is the index of NULL on the params as filled by - * the init function. The two last entries should be - * date and time. Here we overwrite them with the data - * from the CSV header. - */ - - memcpy(params[pnr - 3], ptr, 4); - memcpy(params[pnr - 3] + 4, ptr + 5, 2); - memcpy(params[pnr - 3] + 6, ptr + 8, 2); - params[pnr - 3][8] = 0; - - memcpy(params[pnr - 1] + 1, ptr + 11, 2); - memcpy(params[pnr - 1] + 3, ptr + 14, 2); - params[pnr - 1][5] = 0; - } - - params[pnr++] = NULL; - - /* Move the CSV data to the start of mem buffer */ - memmove(mem.buffer, ptr_old, mem.size - (ptr_old - (char*)mem.buffer)); - mem.size = (int)mem.size - (ptr_old - (char*)mem.buffer); - - if (try_to_xslt_open_csv(filename, &mem, csvtemplate)) - return -1; - - /* - * Lets print command line for manual testing with xsltproc if - * verbosity level is high enough. The printed line needs the - * input file added as last parameter. - */ - - if (verbose >= 2) { - fprintf(stderr, "xsltproc "); - for (i=0; params[i]; i+=2) - fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]); - fprintf(stderr, "xslt/csv2xml.xslt\n"); - } - - ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params); - free(mem.buffer); - for (i = 0; params[i]; i += 2) - free(params[i + 1]); - - return ret; -} - -int parse_manual_file(const char *filename, char **params, int pnr) -{ - struct memblock mem; - time_t now; - struct tm *timep; - char curdate[9]; - char curtime[6]; - int ret, i; - - - time(&now); - timep = localtime(&now); - strftime(curdate, DATESTR, "%Y%m%d", timep); - - /* As the parameter is numeric, we need to ensure that the leading zero - * is not discarded during the transform, thus prepend time with 1 */ - strftime(curtime, TIMESTR, "1%H%M", timep); - - - params[pnr++] = strdup("date"); - params[pnr++] = strdup(curdate); - params[pnr++] = strdup("time"); - params[pnr++] = strdup(curtime); - params[pnr++] = NULL; - - if (filename == NULL) - return report_error("No manual CSV filename"); - - mem.size = 0; - if (try_to_xslt_open_csv(filename, &mem, "manualCSV")) - return -1; - - ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params); - - free(mem.buffer); - for (i = 0; i < pnr - 2; ++i) - free(params[i]); - return ret; -} diff --git a/subsurface-core/file.h b/subsurface-core/file.h deleted file mode 100644 index 1c1dfc116..000000000 --- a/subsurface-core/file.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef FILE_H -#define FILE_H - -struct memblock { - void *buffer; - size_t size; -}; - -extern int try_to_open_cochran(const char *filename, struct memblock *mem); -extern int try_to_open_liquivision(const char *filename, struct memblock *mem); -extern void datatrak_import(const char *file, struct dive_table *table); -extern void ostctools_import(const char *file, struct dive_table *table); - -#ifdef __cplusplus -extern "C" { -#endif -extern int readfile(const char *filename, struct memblock *mem); -extern timestamp_t parse_date(const char *date); -extern int try_to_open_zip(const char *filename); -#ifdef __cplusplus -} -#endif - -#endif // FILE_H diff --git a/subsurface-core/gas-model.c b/subsurface-core/gas-model.c deleted file mode 100644 index ad1160f3b..000000000 --- a/subsurface-core/gas-model.c +++ /dev/null @@ -1,64 +0,0 @@ -/* gas-model.c */ -/* gas compressibility model */ -#include <stdio.h> -#include <stdlib.h> -#include "dive.h" - -/* "Virial minus one" - the virial cubic form without the initial 1.0 */ -#define virial_m1(C, x1, x2, x3) (C[0]*x1+C[1]*x2+C[2]*x3) - -/* - * Cubic virial least-square coefficients for O2/N2/He based on data from - * - * PERRY’S CHEMICAL ENGINEERS’ HANDBOOK SEVENTH EDITION - * - * with the lookup and curve fitting by Lubomir. - * - * The "virial" form of the compression factor polynomial is - * - * Z = 1.0 + C[0]*P + C[1]*P^2 + C[2]*P^3 ... - * - * and these tables do not contain the initial 1.0 term. - * - * NOTE! Helium coefficients are a linear mix operation between the - * 323K and one for 273K isotherms, to make everything be at 300K. - */ -double gas_compressibility_factor(struct gasmix *gas, double bar) -{ - static const double o2_coefficients[3] = { - -7.18092073703e-04, - +2.81852572808e-06, - -1.50290620492e-09 - }; - static const double n2_coefficients[3] = { - -2.19260353292e-04, - +2.92844845532e-06, - -2.07613482075e-09 - }; - static const double he_coefficients[3] = { - +4.87320026468e-04, - -8.83632921053e-08, - +5.33304543646e-11 - }; - int o2, he; - double x1, x2, x3; - double Z; - - o2 = get_o2(gas); - he = get_he(gas); - - x1 = bar; x2 = x1*x1; x3 = x2*x1; - - Z = virial_m1(o2_coefficients, x1, x2, x3) * o2 + - virial_m1(he_coefficients, x1, x2, x3) * he + - virial_m1(n2_coefficients, x1, x2, x3) * (1000 - o2 - he); - - /* - * We add the 1.0 at the very end - the linear mixing of the - * three 1.0 terms is still 1.0 regardless of the gas mix. - * - * The * 0.001 is because we did the linear mixing using the - * raw permille gas values. - */ - return Z * 0.001 + 1.0; -} diff --git a/subsurface-core/gaspressures.c b/subsurface-core/gaspressures.c deleted file mode 100644 index 5d3fc9791..000000000 --- a/subsurface-core/gaspressures.c +++ /dev/null @@ -1,430 +0,0 @@ -/* gaspressures.c - * --------------- - * This file contains the routines to calculate the gas pressures in the cylinders. - * The functions below support the code in profile.c. - * The high-level function is populate_pressure_information(), called by function - * create_plot_info_new() in profile.c. The other functions below are, in turn, - * called by populate_pressure_information(). The calling sequence is as follows: - * - * populate_pressure_information() -> calc_pressure_time() - * -> fill_missing_tank_pressures() -> fill_missing_segment_pressures() - * -> get_pr_interpolate_data() - * - * The pr_track_t related functions below implement a linked list that is used by - * the majority of the functions below. The linked list covers a part of the dive profile - * for which there are no cylinder pressure data. Each element in the linked list - * represents a segment between two consecutive points on the dive profile. - * pr_track_t is defined in gaspressures.h - */ - -#include "dive.h" -#include "display.h" -#include "profile.h" -#include "gaspressures.h" - -static pr_track_t *pr_track_alloc(int start, int t_start) -{ - pr_track_t *pt = malloc(sizeof(pr_track_t)); - pt->start = start; - pt->end = 0; - pt->t_start = pt->t_end = t_start; - pt->pressure_time = 0; - pt->next = NULL; - return pt; -} - -/* poor man's linked list */ -static pr_track_t *list_last(pr_track_t *list) -{ - pr_track_t *tail = list; - if (!tail) - return NULL; - while (tail->next) { - tail = tail->next; - } - return tail; -} - -static pr_track_t *list_add(pr_track_t *list, pr_track_t *element) -{ - pr_track_t *tail = list_last(list); - if (!tail) - return element; - tail->next = element; - return list; -} - -static void list_free(pr_track_t *list) -{ - if (!list) - return; - list_free(list->next); - free(list); -} - -#ifdef DEBUG_PR_TRACK -static void dump_pr_track(pr_track_t **track_pr) -{ - int cyl; - pr_track_t *list; - - for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { - list = track_pr[cyl]; - while (list) { - printf("cyl%d: start %d end %d t_start %d t_end %d pt %d\n", cyl, - list->start, list->end, list->t_start, list->t_end, list->pressure_time); - list = list->next; - } - } -} -#endif - -/* - * This looks at the pressures for one cylinder, and - * calculates any missing beginning/end pressures for - * each segment by taking the over-all SAC-rate into - * account for that cylinder. - * - * NOTE! Many segments have full pressure information - * (both beginning and ending pressure). But if we have - * switched away from a cylinder, we will have the - * beginning pressure for the first segment with a - * missing end pressure. We may then have one or more - * segments without beginning or end pressures, until - * we finally have a segment with an end pressure. - * - * We want to spread out the pressure over these missing - * segments according to how big of a time_pressure area - * they have. - */ -static void fill_missing_segment_pressures(pr_track_t *list, enum interpolation_strategy strategy) -{ - double magic; - - while (list) { - int start = list->start, end; - pr_track_t *tmp = list; - int pt_sum = 0, pt = 0; - - for (;;) { - pt_sum += tmp->pressure_time; - end = tmp->end; - if (end) - break; - end = start; - if (!tmp->next) - break; - tmp = tmp->next; - } - - if (!start) - start = end; - - /* - * Now 'start' and 'end' contain the pressure values - * for the set of segments described by 'list'..'tmp'. - * pt_sum is the sum of all the pressure-times of the - * segments. - * - * Now dole out the pressures relative to pressure-time. - */ - list->start = start; - tmp->end = end; - switch (strategy) { - case SAC: - for (;;) { - int pressure; - pt += list->pressure_time; - pressure = start; - if (pt_sum) - pressure -= (start - end) * (double)pt / pt_sum; - list->end = pressure; - if (list == tmp) - break; - list = list->next; - list->start = pressure; - } - break; - case TIME: - if (list->t_end && (tmp->t_start - tmp->t_end)) { - magic = (list->t_start - tmp->t_end) / (tmp->t_start - tmp->t_end); - list->end = rint(start - (start - end) * magic); - } else { - list->end = start; - } - break; - case CONSTANT: - list->end = start; - } - - /* Ok, we've done that set of segments */ - list = list->next; - } -} - -#ifdef DEBUG_PR_INTERPOLATE -void dump_pr_interpolate(int i, pr_interpolate_t interpolate_pr) -{ - printf("Interpolate for entry %d: start %d - end %d - pt %d - acc_pt %d\n", i, - interpolate_pr.start, interpolate_pr.end, interpolate_pr.pressure_time, interpolate_pr.acc_pressure_time); -} -#endif - - -static struct pr_interpolate_struct get_pr_interpolate_data(pr_track_t *segment, struct plot_info *pi, int cur) -{ // cur = index to pi->entry corresponding to t_end of segment; - struct pr_interpolate_struct interpolate; - int i; - struct plot_data *entry; - - interpolate.start = segment->start; - interpolate.end = segment->end; - interpolate.acc_pressure_time = 0; - interpolate.pressure_time = 0; - - for (i = 0; i < pi->nr; i++) { - entry = pi->entry + i; - - if (entry->sec < segment->t_start) - continue; - interpolate.pressure_time += entry->pressure_time; - if (entry->sec >= segment->t_end) - break; - if (i <= cur) - interpolate.acc_pressure_time += entry->pressure_time; - } - return interpolate; -} - -static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, pr_track_t **track_pr, bool o2_flag) -{ - int cyl, i; - struct plot_data *entry; - pr_interpolate_t interpolate = { 0, 0, 0, 0 }; - pr_track_t *last_segment = NULL; - int cur_pr[MAX_CYLINDERS]; // cur_pr[MAX_CYLINDERS] is the CCR diluent cylinder - - for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { - enum interpolation_strategy strategy; - if (!track_pr[cyl]) { - /* no segment where this cylinder is used */ - cur_pr[cyl] = -1; - continue; - } - if (dive->cylinder[cyl].cylinder_use == OC_GAS) - strategy = SAC; - else - strategy = TIME; - fill_missing_segment_pressures(track_pr[cyl], strategy); // Interpolate the missing tank pressure values .. - cur_pr[cyl] = track_pr[cyl]->start; // in the pr_track_t lists of structures - } // and keep the starting pressure for each cylinder. - -#ifdef DEBUG_PR_TRACK - /* another great debugging tool */ - dump_pr_track(track_pr); -#endif - - /* Transfer interpolated cylinder pressures from pr_track strucktures to plotdata - * Go down the list of tank pressures in plot_info. Align them with the start & - * end times of each profile segment represented by a pr_track_t structure. Get - * the accumulated pressure_depths from the pr_track_t structures and then - * interpolate the pressure where these do not exist in the plot_info pressure - * variables. Pressure values are transferred from the pr_track_t structures - * to the plot_info structure, allowing us to plot the tank pressure. - * - * The first two pi structures are "fillers", but in case we don't have a sample - * at time 0 we need to process the second of them here, therefore i=1 */ - for (i = 1; i < pi->nr; i++) { // For each point on the profile: - double magic; - pr_track_t *segment; - int pressure; - int *save_pressure, *save_interpolated; - - entry = pi->entry + i; - - if (o2_flag) { - // Find the cylinder index (cyl) and pressure - cyl = dive->oxygen_cylinder_index; - if (cyl < 0) - return; // Can we do this?!? - pressure = O2CYLINDER_PRESSURE(entry); - save_pressure = &(entry->o2cylinderpressure[SENSOR_PR]); - save_interpolated = &(entry->o2cylinderpressure[INTERPOLATED_PR]); - } else { - pressure = SENSOR_PRESSURE(entry); - save_pressure = &(entry->pressure[SENSOR_PR]); - save_interpolated = &(entry->pressure[INTERPOLATED_PR]); - cyl = entry->cylinderindex; - } - - if (pressure) { // If there is a valid pressure value, - last_segment = NULL; // get rid of interpolation data, - cur_pr[cyl] = pressure; // set current pressure - continue; // and skip to next point. - } - // If there is NO valid pressure value.. - // Find the pressure segment corresponding to this entry.. - segment = track_pr[cyl]; - while (segment && segment->t_end < entry->sec) // Find the track_pr with end time.. - segment = segment->next; // ..that matches the plot_info time (entry->sec) - - if (!segment || !segment->pressure_time) { // No (or empty) segment? - *save_pressure = cur_pr[cyl]; // Just use our current pressure - continue; // and skip to next point. - } - - // If there is a valid segment but no tank pressure .. - if (segment == last_segment) { - interpolate.acc_pressure_time += entry->pressure_time; - } else { - // Set up an interpolation structure - interpolate = get_pr_interpolate_data(segment, pi, i); - last_segment = segment; - } - - if(dive->cylinder[cyl].cylinder_use == OC_GAS) { - - /* if this segment has pressure_time, then calculate a new interpolated pressure */ - if (interpolate.pressure_time) { - /* Overall pressure change over total pressure-time for this segment*/ - magic = (interpolate.end - interpolate.start) / (double)interpolate.pressure_time; - - /* Use that overall pressure change to update the current pressure */ - cur_pr[cyl] = rint(interpolate.start + magic * interpolate.acc_pressure_time); - } - } else { - magic = (interpolate.end - interpolate.start) / (segment->t_end - segment->t_start); - cur_pr[cyl] = rint(segment->start + magic * (entry->sec - segment->t_start)); - } - *save_interpolated = cur_pr[cyl]; // and store the interpolated data in plot_info - } -} - - -/* - * What's the pressure-time between two plot data entries? - * We're calculating the integral of pressure over time by - * adding these up. - * - * The units won't matter as long as everybody agrees about - * them, since they'll cancel out - we use this to calculate - * a constant SAC-rate-equivalent, but we only use it to - * scale pressures, so it ends up being a unitless scaling - * factor. - */ -static inline int calc_pressure_time(struct dive *dive, struct plot_data *a, struct plot_data *b) -{ - int time = b->sec - a->sec; - int depth = (a->depth + b->depth) / 2; - - if (depth <= SURFACE_THRESHOLD) - return 0; - - return depth_to_mbar(depth, dive) * time; -} - -#ifdef PRINT_PRESSURES_DEBUG -// A CCR debugging tool that prints the gas pressures in cylinder 0 and in the diluent cylinder, used in populate_pressure_information(): -static void debug_print_pressures(struct plot_info *pi) -{ - int i; - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - printf("%5d |%9d | %9d || %9d | %9d |\n", i, SENSOR_PRESSURE(entry), INTERPOLATED_PRESSURE(entry), DILUENT_PRESSURE(entry), INTERPOLATED_DILUENT_PRESSURE(entry)); - } -} -#endif - -/* This function goes through the list of tank pressures, either SENSOR_PRESSURE(entry) or O2CYLINDER_PRESSURE(entry), - * of structure plot_info for the dive profile where each item in the list corresponds to one point (node) of the - * profile. It finds values for which there are no tank pressures (pressure==0). For each missing item (node) of - * tank pressure it creates a pr_track_alloc structure that represents a segment on the dive profile and that - * contains tank pressures. There is a linked list of pr_track_alloc structures for each cylinder. These pr_track_alloc - * structures ultimately allow for filling the missing tank pressure values on the dive profile using the depth_pressure - * of the dive. To do this, it calculates the summed pressure-time value for the duration of the dive and stores these - * in the pr_track_alloc structures. If diluent_flag = 1, then DILUENT_PRESSURE(entry) is used instead of SENSOR_PRESSURE. - * This function is called by create_plot_info_new() in profile.c - */ -void populate_pressure_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, int o2_flag) -{ - (void) dc; - int i, cylinderid, cylinderindex = -1; - pr_track_t *track_pr[MAX_CYLINDERS] = { NULL, }; - pr_track_t *current = NULL; - bool missing_pr = false; - bool found_any_pr_data = false; - - /* if we have no pressure data whatsoever, this is pointless, so let's just return */ - for (i = 0; i < MAX_CYLINDERS; i++) { - if (dive->cylinder[i].start.mbar || dive->cylinder[i].sample_start.mbar || - dive->cylinder[i].end.mbar || dive->cylinder[i].sample_end.mbar) { - found_any_pr_data = true; - break; - } - } - if (!found_any_pr_data) - return; - - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - unsigned pressure; - if (o2_flag) { // if this is a diluent cylinder: - pressure = O2CYLINDER_PRESSURE(entry); - cylinderid = dive->oxygen_cylinder_index; - if (cylinderid < 0) - goto GIVE_UP; - } else { - pressure = SENSOR_PRESSURE(entry); - cylinderid = entry->cylinderindex; - } - /* If track_pr structure already exists, then update it: */ - /* discrete integration of pressure over time to get the SAC rate equivalent */ - if (current) { - entry->pressure_time = calc_pressure_time(dive, entry - 1, entry); - current->pressure_time += entry->pressure_time; - current->t_end = entry->sec; - } - - /* If 1st record or different cylinder: Create a new track_pr structure: */ - /* track the segments per cylinder and their pressure/time integral */ - if (cylinderid != cylinderindex) { - if (o2_flag) // For CCR dives: - cylinderindex = dive->oxygen_cylinder_index; // indicate o2 cylinder - else - cylinderindex = entry->cylinderindex; - current = pr_track_alloc(pressure, entry->sec); - track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current); - continue; - } - - if (!pressure) { - missing_pr = 1; - continue; - } - if (current) - current->end = pressure; - - /* Was it continuous? */ - if ((o2_flag) && (O2CYLINDER_PRESSURE(entry - 1))) // in the case of CCR o2 pressure - continue; - else if (SENSOR_PRESSURE(entry - 1)) // for all other cylinders - continue; - - /* transmitter stopped transmitting cylinder pressure data */ - current = pr_track_alloc(pressure, entry->sec); - if (cylinderindex >= 0) - track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current); - } - - if (missing_pr) { - fill_missing_tank_pressures(dive, pi, track_pr, o2_flag); - } - -#ifdef PRINT_PRESSURES_DEBUG - debug_print_pressures(pi); -#endif - -GIVE_UP: - for (i = 0; i < MAX_CYLINDERS; i++) - list_free(track_pr[i]); -} diff --git a/subsurface-core/gaspressures.h b/subsurface-core/gaspressures.h deleted file mode 100644 index 420c117a2..000000000 --- a/subsurface-core/gaspressures.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef GASPRESSURES_H -#define GASPRESSURES_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * simple structure to track the beginning and end tank pressure as - * well as the integral of depth over time spent while we have no - * pressure reading from the tank */ -typedef struct pr_track_struct pr_track_t; -struct pr_track_struct { - int start; - int end; - int t_start; - int t_end; - int pressure_time; - pr_track_t *next; -}; - -typedef struct pr_interpolate_struct pr_interpolate_t; -struct pr_interpolate_struct { - int start; - int end; - int pressure_time; - int acc_pressure_time; -}; - -enum interpolation_strategy {SAC, TIME, CONSTANT}; - -#ifdef __cplusplus -} -#endif -#endif // GASPRESSURES_H diff --git a/subsurface-core/gettext.h b/subsurface-core/gettext.h deleted file mode 100644 index 43ff023c7..000000000 --- a/subsurface-core/gettext.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef MYGETTEXT_H -#define MYGETTEXT_H - -/* this is for the Qt based translations */ -extern const char *trGettext(const char *); -#define translate(_context, arg) trGettext(arg) -#define QT_TRANSLATE_NOOP(_context, arg) arg -#define QT_TRANSLATE_NOOP3(_context, arg, _comment) arg - -#endif // MYGETTEXT_H diff --git a/subsurface-core/gettextfromc.cpp b/subsurface-core/gettextfromc.cpp deleted file mode 100644 index c579e3c3c..000000000 --- a/subsurface-core/gettextfromc.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include <QCoreApplication> -#include <QString> -#include <gettextfromc.h> - -const char *gettextFromC::trGettext(const char *text) -{ - QByteArray &result = translationCache[QByteArray(text)]; - if (result.isEmpty()) - result = translationCache[QByteArray(text)] = trUtf8(text).toUtf8(); - return result.constData(); -} - -void gettextFromC::reset(void) -{ - translationCache.clear(); -} - -gettextFromC *gettextFromC::instance() -{ - static QScopedPointer<gettextFromC> self(new gettextFromC()); - return self.data(); -} - -extern "C" const char *trGettext(const char *text) -{ - return gettextFromC::instance()->trGettext(text); -} diff --git a/subsurface-core/gettextfromc.h b/subsurface-core/gettextfromc.h deleted file mode 100644 index 53df18365..000000000 --- a/subsurface-core/gettextfromc.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef GETTEXTFROMC_H -#define GETTEXTFROMC_H - -#include <QHash> -#include <QCoreApplication> - -extern "C" const char *trGettext(const char *text); - -class gettextFromC { - Q_DECLARE_TR_FUNCTIONS(gettextFromC) -public: - static gettextFromC *instance(); - const char *trGettext(const char *text); - void reset(void); - QHash<QByteArray, QByteArray> translationCache; -}; - -#endif // GETTEXTFROMC_H diff --git a/subsurface-core/git-access.c b/subsurface-core/git-access.c deleted file mode 100644 index d10139d3d..000000000 --- a/subsurface-core/git-access.c +++ /dev/null @@ -1,929 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include <stdio.h> -#include <ctype.h> -#include <string.h> -#include <stdlib.h> -#include <errno.h> -#include <time.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <fcntl.h> -#include <git2.h> - -#include "dive.h" -#include "membuffer.h" -#include "strndup.h" -#include "qthelperfromc.h" -#include "git-access.h" -#include "gettext.h" - -bool is_subsurface_cloud = false; - -int (*update_progress_cb)(int, const char *) = NULL; - -void set_git_update_cb(int(*cb)(int, const char *)) -{ - update_progress_cb = cb; -} - -// total overkill, but this allows us to get good timing in various scenarios; -// the various parts of interacting with the local and remote git repositories send -// us updates which indicate progress (and no, this is not smooth and definitely not -// proportional - some parts are based on compute performance, some on network speed) -// they also provide information where in the process we are so we can analyze the log -// to understand which parts of the process take how much time. - -// last_git_storage_update_val is used to detect when we suddenly go back to smaller -// "percentage" value because we are back to executing earlier code a second (or third -// time) in that case a negative percentage value is sent to the callback function as a -// special case to mark that situation. Overall this ensures monotonous percentage values -int last_git_storage_update_val; - -int git_storage_update_progress(int percent, const char *text) -{ - static int delta = 0; - - if (percent == 0) { - delta = 0; - } else if (percent > 0 && percent < last_git_storage_update_val) { - delta = last_git_storage_update_val + delta; - if (update_progress_cb) - (*update_progress_cb)(-delta, "DELTA"); - if (verbose) - fprintf(stderr, "set git storage percentage delta to %d\n", delta); - } - - last_git_storage_update_val = percent; - - percent += delta; - - int ret = 0; - if (update_progress_cb) - ret = (*update_progress_cb)(percent, text); - return ret; -} - -// the checkout_progress_cb doesn't allow canceling of the operation -// map the git progress to 70..90% of overall progress -static void progress_cb(const char *path, size_t completed_steps, size_t total_steps, void *payload) -{ - (void) path; - (void) payload; - - int percent = 0; - if (total_steps) - percent = 70 + 20 * completed_steps / total_steps; - (void)git_storage_update_progress(percent, "checkout_progress_cb"); -} - -// this randomly assumes that 80% of the time is spent on the objects and 20% on the deltas -// map the git progress to 70..90% of overall progress -// if the user cancels the dialog this is passed back to libgit2 -static int transfer_progress_cb(const git_transfer_progress *stats, void *payload) -{ - (void) payload; - - int percent = 0; - if (stats->total_objects) - percent = 70 + 16 * stats->received_objects / stats->total_objects; - if (stats->total_deltas) - percent += 4 * stats->indexed_deltas / stats->total_deltas; - /* for debugging this is useful - char buf[100]; - snprintf(buf, 100, "transfer cb rec_obj %d tot_obj %d idx_delta %d total_delta %d local obj %d", stats->received_objects, stats->total_objects, stats->indexed_deltas, stats->total_deltas, stats->local_objects); - return git_storage_update_progress(percent, buf); - */ - return git_storage_update_progress(percent, "transfer cb"); -} - -// the initial push to sync the repos is mapped to 10..15% of overall progress -static int push_transfer_progress_cb(unsigned int current, unsigned int total, size_t bytes, void *payload) -{ - (void) bytes; - (void) payload; - - int percent = 0; - if (total != 0) - percent = 12 + 5 * current / total; - return git_storage_update_progress(percent, "push trasfer cb"); -} - -char *get_local_dir(const char *remote, const char *branch) -{ - SHA_CTX ctx; - unsigned char hash[20]; - - // That zero-byte update is so that we don't get hash - // collisions for "repo1 branch" vs "repo 1branch". - SHA1_Init(&ctx); - SHA1_Update(&ctx, remote, strlen(remote)); - SHA1_Update(&ctx, "", 1); - SHA1_Update(&ctx, branch, strlen(branch)); - SHA1_Final(hash, &ctx); - - return format_string("%s/cloudstorage/%02x%02x%02x%02x%02x%02x%02x%02x", - system_default_directory(), - hash[0], hash[1], hash[2], hash[3], - hash[4], hash[5], hash[6], hash[7]); -} - -static char *move_local_cache(const char *remote, const char *branch) -{ - char *old_path = get_local_dir(remote, branch); - return move_away(old_path); -} - -static int check_clean(const char *path, unsigned int status, void *payload) -{ - (void) payload; - status &= ~GIT_STATUS_CURRENT | GIT_STATUS_IGNORED; - if (!status) - return 0; - if (is_subsurface_cloud) - report_error(translate("gettextFromC", "Local cache directory %s corrupted - can't sync with Subsurface cloud storage"), path); - else - report_error("WARNING: Git cache directory modified (path %s) status %0x", path, status); - return 1; -} - -/* - * The remote is strictly newer than the local branch. - */ -static int reset_to_remote(git_repository *repo, git_reference *local, const git_oid *new_id) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.progress_cb = &progress_cb; - git_object *target; - - if (verbose) - fprintf(stderr, "git storage: reset to remote\n"); - - // If it's not checked out (bare or not HEAD), just update the reference */ - if (git_repository_is_bare(repo) || git_branch_is_head(local) != 1) { - git_reference *out; - - if (git_reference_set_target(&out, local, new_id, "Update to remote")) - return report_error(translate("gettextFromC", "Could not update local cache to newer remote data")); - - git_reference_free(out); - -#ifdef DEBUG - // Not really an error, just informational - report_error("Updated local branch from remote"); -#endif - return 0; - } - - if (git_object_lookup(&target, repo, new_id, GIT_OBJ_COMMIT)) { - if (is_subsurface_cloud) - return report_error(translate("gettextFromC", "Subsurface cloud storage corrupted")); - else - return report_error("Could not look up remote commit"); - } - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - if (git_reset(repo, target, GIT_RESET_HARD, &opts)) { - if (is_subsurface_cloud) - return report_error(translate("gettextFromC", "Could not update local cache to newer remote data")); - else - return report_error("Local head checkout failed after update"); - } - // Not really an error, just informational -#ifdef DEBUG - report_error("Updated local information from remote"); -#endif - return 0; -} - -int credential_ssh_cb(git_cred **out, - const char *url, - const char *username_from_url, - unsigned int allowed_types, - void *payload) -{ - (void) url; - (void) allowed_types; - (void) payload; - - const char *priv_key = format_string("%s/%s", system_default_directory(), "ssrf_remote.key"); - const char *passphrase = prefs.cloud_storage_password ? strdup(prefs.cloud_storage_password) : strdup(""); - return git_cred_ssh_key_new(out, username_from_url, NULL, priv_key, passphrase); -} - -int credential_https_cb(git_cred **out, - const char *url, - const char *username_from_url, - unsigned int allowed_types, - void *payload) -{ - (void) url; - (void) username_from_url; - (void) payload; - (void) allowed_types; - const char *username = prefs.cloud_storage_email_encoded; - const char *password = prefs.cloud_storage_password ? strdup(prefs.cloud_storage_password) : strdup(""); - return git_cred_userpass_plaintext_new(out, username, password); -} - -#define KNOWN_CERT "\xfd\xb8\xf7\x73\x76\xe2\x75\x53\x93\x37\xdc\xfe\x1e\x55\x43\x3d\xf2\x2c\x18\x2c" -int certificate_check_cb(git_cert *cert, int valid, const char *host, void *payload) -{ - (void) payload; - if (same_string(host, "cloud.subsurface-divelog.org") && cert->cert_type == GIT_CERT_X509) { - SHA_CTX ctx; - unsigned char hash[21]; - git_cert_x509 *cert509 = (git_cert_x509 *)cert; - SHA1_Init(&ctx); - SHA1_Update(&ctx, cert509->data, cert509->len); - SHA1_Final(hash, &ctx); - hash[20] = 0; - if (verbose > 1) - if (same_string((char *)hash, KNOWN_CERT)) { - fprintf(stderr, "cloud certificate considered %s, forcing it valid\n", - valid ? "valid" : "not valid"); - return 1; - } - } - return valid; -} - -static int update_remote(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, enum remote_transport rt) -{ - (void) repo; - (void) remote; - - git_push_options opts = GIT_PUSH_OPTIONS_INIT; - git_strarray refspec; - const char *name = git_reference_name(local); - - if (verbose) - fprintf(stderr, "git storage: update remote\n"); - - refspec.count = 1; - refspec.strings = (char **)&name; - - opts.callbacks.push_transfer_progress = &push_transfer_progress_cb; - if (rt == RT_SSH) - opts.callbacks.credentials = credential_ssh_cb; - else if (rt == RT_HTTPS) - opts.callbacks.credentials = credential_https_cb; - opts.callbacks.certificate_check = certificate_check_cb; - - if (git_remote_push(origin, &refspec, &opts)) { - if (is_subsurface_cloud) - return report_error(translate("gettextFromC", "Could not update Subsurface cloud storage, try again later")); - else - return report_error("Unable to update remote with current local cache state (%s)", giterr_last()->message); - } - return 0; -} - -extern int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree); - -static int try_to_git_merge(git_repository *repo, git_reference **local_p, git_reference *remote, git_oid *base, const git_oid *local_id, const git_oid *remote_id) -{ - (void) remote; - git_tree *local_tree, *remote_tree, *base_tree; - git_commit *local_commit, *remote_commit, *base_commit; - git_index *merged_index; - git_merge_options merge_options; - - if (verbose) { - char outlocal[41], outremote[41]; - outlocal[40] = outremote[40] = 0; - git_oid_fmt(outlocal, local_id); - git_oid_fmt(outremote, remote_id); - fprintf(stderr, "trying to merge local SHA %s remote SHA %s\n", outlocal, outremote); - } - - git_merge_init_options(&merge_options, GIT_MERGE_OPTIONS_VERSION); - merge_options.tree_flags = GIT_MERGE_TREE_FIND_RENAMES; - merge_options.file_favor = GIT_MERGE_FILE_FAVOR_UNION; - merge_options.rename_threshold = 100; - if (git_commit_lookup(&local_commit, repo, local_id)) { - fprintf(stderr, "Remote storage and local data diverged. Error: can't get commit (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_commit_tree(&local_tree, local_commit)) { - fprintf(stderr, "Remote storage and local data diverged. Error: failed local tree lookup (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_commit_lookup(&remote_commit, repo, remote_id)) { - fprintf(stderr, "Remote storage and local data diverged. Error: can't get commit (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_commit_tree(&remote_tree, remote_commit)) { - fprintf(stderr, "Remote storage and local data diverged. Error: failed local tree lookup (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_commit_lookup(&base_commit, repo, base)) { - fprintf(stderr, "Remote storage and local data diverged. Error: can't get commit (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_commit_tree(&base_tree, base_commit)) { - fprintf(stderr, "Remote storage and local data diverged. Error: failed base tree lookup (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_merge_trees(&merged_index, repo, base_tree, local_tree, remote_tree, &merge_options)) { - fprintf(stderr, "Remote storage and local data diverged. Error: merge failed (%s)", giterr_last()->message); - // this is the one where I want to report more detail to the user - can't quite explain why - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge failed (%s)"), giterr_last()->message); - } - if (git_index_has_conflicts(merged_index)) { - int error; - const git_index_entry *ancestor = NULL, - *ours = NULL, - *theirs = NULL; - git_index_conflict_iterator *iter = NULL; - error = git_index_conflict_iterator_new(&iter, merged_index); - while (git_index_conflict_next(&ancestor, &ours, &theirs, iter) - != GIT_ITEROVER) { - /* Mark this conflict as resolved */ - fprintf(stderr, "conflict in %s / %s / %s -- ", - ours ? ours->path : "-", - theirs ? theirs->path : "-", - ancestor ? ancestor->path : "-"); - if ((!ours && theirs && ancestor) || - (ours && !theirs && ancestor)) { - // the file was removed on one side or the other - just remove it - fprintf(stderr, "looks like a delete on one side; removing the file from the index\n"); - error = git_index_remove(merged_index, ours ? ours->path : theirs->path, GIT_INDEX_STAGE_ANY); - } else if (ancestor) { - error = git_index_conflict_remove(merged_index, ours ? ours->path : theirs ? theirs->path : ancestor->path); - } - if (error) { - fprintf(stderr, "error at conflict resplution (%s)", giterr_last()->message); - } - } - git_index_conflict_cleanup(merged_index); - git_index_conflict_iterator_free(iter); - report_error(translate("gettextFromC", "Remote storage and local data diverged. Cannot combine local and remote changes")); - } - git_oid merge_oid, commit_oid; - git_tree *merged_tree; - git_signature *author; - git_commit *commit; - - if (git_index_write_tree_to(&merge_oid, merged_index, repo)) - goto write_error; - if (git_tree_lookup(&merged_tree, repo, &merge_oid)) - goto write_error; - if (git_signature_default(&author, repo) < 0) - if (git_signature_now(&author, "Subsurface", "noemail@given") < 0) - goto write_error; - if (git_commit_create_v(&commit_oid, repo, NULL, author, author, NULL, "automatic merge", merged_tree, 2, local_commit, remote_commit)) - goto write_error; - if (git_commit_lookup(&commit, repo, &commit_oid)) - goto write_error; - if (git_branch_is_head(*local_p) && !git_repository_is_bare(repo)) { - git_object *parent; - git_reference_peel(&parent, *local_p, GIT_OBJ_COMMIT); - if (update_git_checkout(repo, parent, merged_tree)) { - goto write_error; - } - } - if (git_reference_set_target(local_p, *local_p, &commit_oid, "Subsurface merge event")) - goto write_error; - set_git_id(&commit_oid); - git_signature_free(author); - if (verbose) - fprintf(stderr, "Successfully merged repositories"); - return 0; - -diverged_error: - return report_error(translate("gettextFromC", "Remote storage and local data diverged")); - -write_error: - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: writing the data failed (%s)"), giterr_last()->message); -} - -// if accessing the local cache of Subsurface cloud storage fails, we simplify things -// for the user and simply move the cache away (in case they want to try and extract data) -// and ask them to retry the operation (which will then refresh the data from the cloud server) -static int cleanup_local_cache(const char *remote_url, const char *branch) -{ - char *backup_path = move_local_cache(remote_url, branch); - report_error(translate("gettextFromC", "Problems with local cache of Subsurface cloud data")); - report_error(translate("gettextFromC", "Moved cache data to %s. Please try the operation again."), backup_path); - free(backup_path); - return -1; -} -static int try_to_update(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, - const char *remote_url, const char *branch, enum remote_transport rt) -{ - git_oid base; - const git_oid *local_id, *remote_id; - int ret = 0; - - if (verbose) - fprintf(stderr, "git storage: try to update\n"); - git_storage_update_progress(9, "try to update"); - if (!git_reference_cmp(local, remote)) - return 0; - - // Dirty modified state in the working tree? We're not going - // to update either way - if (git_status_foreach(repo, check_clean, NULL)) { - if (is_subsurface_cloud) - goto cloud_data_error; - else - return report_error("local cached copy is dirty, skipping update"); - } - local_id = git_reference_target(local); - remote_id = git_reference_target(remote); - - if (!local_id || !remote_id) { - if (is_subsurface_cloud) - goto cloud_data_error; - else - return report_error("Unable to get local or remote SHA1"); - } - if (git_merge_base(&base, repo, local_id, remote_id)) { - if (is_subsurface_cloud) - goto cloud_data_error; - else - return report_error("Unable to find common commit of local and remote branches"); - } - /* Is the remote strictly newer? Use it */ - if (git_oid_equal(&base, local_id)) - return reset_to_remote(repo, local, remote_id); - - /* Is the local repo the more recent one? See if we can update upstream */ - if (git_oid_equal(&base, remote_id)) { - if (verbose) - fprintf(stderr, "local is newer than remote, update remote\n"); - git_storage_update_progress(10, "git_update_remote, local was newer"); - return update_remote(repo, origin, local, remote, rt); - } - /* Merging a bare repository always needs user action */ - if (git_repository_is_bare(repo)) { - if (is_subsurface_cloud) - goto cloud_data_error; - else - return report_error("Local and remote have diverged, merge of bare branch needed"); - } - /* Merging will definitely need the head branch too */ - if (git_branch_is_head(local) != 1) { - if (is_subsurface_cloud) - goto cloud_data_error; - else - return report_error("Local and remote do not match, local branch not HEAD - cannot update"); - } - /* Ok, let's try to merge these */ - git_storage_update_progress(11, "try to merge"); - ret = try_to_git_merge(repo, &local, remote, &base, local_id, remote_id); - if (ret == 0) - return update_remote(repo, origin, local, remote, rt); - else - return ret; - -cloud_data_error: - // since we are working with Subsurface cloud storage we want to make the user interaction - // as painless as possible. So if something went wrong with the local cache, tell the user - // about it an move it away - return cleanup_local_cache(remote_url, branch); -} - -static int check_remote_status(git_repository *repo, git_remote *origin, const char *remote, const char *branch, enum remote_transport rt) -{ - int error = 0; - - git_reference *local_ref, *remote_ref; - - if (verbose) - fprintf(stderr, "git storage: check remote status\n"); - git_storage_update_progress(7, "git check remote status"); - - if (git_branch_lookup(&local_ref, repo, branch, GIT_BRANCH_LOCAL)) { - if (is_subsurface_cloud) - return cleanup_local_cache(remote, branch); - else - return report_error("Git cache branch %s no longer exists", branch); - } - if (git_branch_upstream(&remote_ref, local_ref)) { - /* so there is no upstream branch for our branch; that's a problem. - * let's push our branch */ - git_strarray refspec; - git_reference_list(&refspec, repo); - git_push_options opts = GIT_PUSH_OPTIONS_INIT; - opts.callbacks.transfer_progress = &transfer_progress_cb; - if (rt == RT_SSH) - opts.callbacks.credentials = credential_ssh_cb; - else if (rt == RT_HTTPS) - opts.callbacks.credentials = credential_https_cb; - opts.callbacks.certificate_check = certificate_check_cb; - git_storage_update_progress(8, "git remote push (no remote existed)"); - error = git_remote_push(origin, &refspec, &opts); - } else { - error = try_to_update(repo, origin, local_ref, remote_ref, remote, branch, rt); - git_reference_free(remote_ref); - } - git_reference_free(local_ref); - return error; -} - -int sync_with_remote(git_repository *repo, const char *remote, const char *branch, enum remote_transport rt) -{ - int error; - git_remote *origin; - char *proxy_string; - git_config *conf; - - if (prefs.git_local_only) { - if (verbose) - fprintf(stderr, "don't sync with remote - read from cache only\n"); - return 0; - } - if (verbose) - fprintf(stderr, "sync with remote %s[%s]\n", remote, branch); - git_storage_update_progress(2, "sync with remote"); - git_repository_config(&conf, repo); - if (rt == RT_HTTPS && getProxyString(&proxy_string)) { - if (verbose) - fprintf(stderr, "set proxy to \"%s\"\n", proxy_string); - git_config_set_string(conf, "http.proxy", proxy_string); - free(proxy_string); - } else { - if (verbose) - fprintf(stderr, "delete proxy setting\n"); - git_config_delete_entry(conf, "http.proxy"); - } - - /* - * NOTE! Remote errors are reported, but are nonfatal: - * we still successfully return the local repository. - */ - error = git_remote_lookup(&origin, repo, "origin"); - if (error) { - if (!is_subsurface_cloud) - report_error("Repository '%s' origin lookup failed (%s)", remote, giterr_last()->message); - return 0; - } - - if (rt == RT_HTTPS && !canReachCloudServer()) { - // this is not an error, just a warning message, so return 0 - report_error("Cannot connect to cloud server, working with local copy"); - git_storage_update_progress(18, "can't reach cloud server, working with local copy"); - return 0; - } - if (verbose) - fprintf(stderr, "git storage: fetch remote\n"); - git_fetch_options opts = GIT_FETCH_OPTIONS_INIT; - opts.callbacks.transfer_progress = &transfer_progress_cb; - if (rt == RT_SSH) - opts.callbacks.credentials = credential_ssh_cb; - else if (rt == RT_HTTPS) - opts.callbacks.credentials = credential_https_cb; - opts.callbacks.certificate_check = certificate_check_cb; - git_storage_update_progress(6, "git fetch remote"); - error = git_remote_fetch(origin, NULL, &opts, NULL); - // NOTE! A fetch error is not fatal, we just report it - if (error) { - if (is_subsurface_cloud) - report_error("Cannot sync with cloud server, working with offline copy"); - else - report_error("Unable to fetch remote '%s'", remote); - if (verbose) - fprintf(stderr, "remote fetch failed (%s)\n", giterr_last()->message); - error = 0; - } else { - error = check_remote_status(repo, origin, remote, branch, rt); - } - git_remote_free(origin); - git_storage_update_progress(18, "done with sync with remote"); - return error; -} - -static git_repository *update_local_repo(const char *localdir, const char *remote, const char *branch, enum remote_transport rt) -{ - int error; - git_repository *repo = NULL; - - if (verbose) - fprintf(stderr, "git storage: update local repo\n"); - - error = git_repository_open(&repo, localdir); - if (error) { - if (is_subsurface_cloud) - (void)cleanup_local_cache(remote, branch); - else - report_error("Unable to open git cache repository at %s: %s", localdir, giterr_last()->message); - return NULL; - } - sync_with_remote(repo, remote, branch, rt); - return repo; -} - -static int repository_create_cb(git_repository **out, const char *path, int bare, void *payload) -{ - (void) payload; - char *proxy_string; - git_config *conf; - - int ret = git_repository_init(out, path, bare); - - git_repository_config(&conf, *out); - if (getProxyString(&proxy_string)) { - if (verbose) - fprintf(stderr, "set proxy to \"%s\"\n", proxy_string); - git_config_set_string(conf, "http.proxy", proxy_string); - free(proxy_string); - } else { - if (verbose) - fprintf(stderr, "delete proxy setting\n"); - git_config_delete_entry(conf, "http.proxy"); - } - return ret; -} - -/* this should correctly initialize both the local and remote - * repository for the Subsurface cloud storage */ -static git_repository *create_and_push_remote(const char *localdir, const char *remote, const char *branch) -{ - git_repository *repo; - git_config *conf; - int len; - char *variable_name, *merge_head; - - if (verbose) - fprintf(stderr, "git storage: create and push remote\n"); - - /* first make sure the directory for the local cache exists */ - subsurface_mkdir(localdir); - - /* set up the origin to point to our remote */ - git_repository_init_options init_opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - init_opts.origin_url = remote; - - /* now initialize the repository with */ - git_repository_init_ext(&repo, localdir, &init_opts); - - /* create a config so we can set the remote tracking branch */ - git_repository_config(&conf, repo); - len = sizeof("branch..remote") + strlen(branch); - variable_name = malloc(len); - snprintf(variable_name, len, "branch.%s.remote", branch); - git_config_set_string(conf, variable_name, "origin"); - /* we know this is shorter than the previous one, so we reuse the variable*/ - snprintf(variable_name, len, "branch.%s.merge", branch); - len = sizeof("refs/heads/") + strlen(branch); - merge_head = malloc(len); - snprintf(merge_head, len, "refs/heads/%s", branch); - git_config_set_string(conf, variable_name, merge_head); - - /* finally create an empty commit and push it to the remote */ - if (do_git_save(repo, branch, remote, false, true)) - return NULL; - return(repo); -} - -static git_repository *create_local_repo(const char *localdir, const char *remote, const char *branch, enum remote_transport rt) -{ - int error; - git_repository *cloned_repo = NULL; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - - if (verbose) - fprintf(stderr, "git storage: create_local_repo\n"); - - opts.fetch_opts.callbacks.transfer_progress = &transfer_progress_cb; - if (rt == RT_SSH) - opts.fetch_opts.callbacks.credentials = credential_ssh_cb; - else if (rt == RT_HTTPS) - opts.fetch_opts.callbacks.credentials = credential_https_cb; - opts.repository_cb = repository_create_cb; - opts.fetch_opts.callbacks.certificate_check = certificate_check_cb; - - opts.checkout_branch = branch; - if (rt == RT_HTTPS && !canReachCloudServer()) - return 0; - if (verbose > 1) - fprintf(stderr, "git storage: calling git_clone()\n"); - error = git_clone(&cloned_repo, remote, localdir, &opts); - if (verbose > 1) - fprintf(stderr, "git storage: returned from git_clone() with error %d\n", error); - if (error) { - char *msg = giterr_last()->message; - int len = sizeof("Reference 'refs/remotes/origin/' not found") + strlen(branch); - char *pattern = malloc(len); - snprintf(pattern, len, "Reference 'refs/remotes/origin/%s' not found", branch); - if (strstr(remote, prefs.cloud_git_url) && strstr(msg, pattern)) { - /* we're trying to open the remote branch that corresponds - * to our cloud storage and the branch doesn't exist. - * So we need to create the branch and push it to the remote */ - cloned_repo = create_and_push_remote(localdir, remote, branch); -#if !defined(DEBUG) && !defined(SUBSURFACE_MOBILE) - } else if (is_subsurface_cloud) { - report_error(translate("gettextFromC", "Error connecting to Subsurface cloud storage")); -#endif - } else { - report_error(translate("gettextFromC", "git clone of %s failed (%s)"), remote, msg); - } - free(pattern); - } - return cloned_repo; -} - -static struct git_repository *get_remote_repo(const char *localdir, const char *remote, const char *branch) -{ - struct stat st; - enum remote_transport rt; - - /* figure out the remote transport */ - if (strncmp(remote, "ssh://", 6) == 0) - rt = RT_SSH; - else if (strncmp(remote, "https://", 8) == 0) - rt = RT_HTTPS; - else - rt = RT_OTHER; - - if (verbose > 1) { - fprintf(stderr, "git_remote_repo: accessing %s\n", remote); - } - git_storage_update_progress(1, "start git interaction"); - /* Do we already have a local cache? */ - if (!stat(localdir, &st)) { - if (!S_ISDIR(st.st_mode)) { - if (is_subsurface_cloud) - (void)cleanup_local_cache(remote, branch); - else - report_error("local git cache at '%s' is corrupt"); - return NULL; - } - return update_local_repo(localdir, remote, branch, rt); - } - if (!prefs.git_local_only) - return create_local_repo(localdir, remote, branch, rt); - else - return 0; - -} - -/* - * This turns a remote repository into a local one if possible. - * - * The recognized formats are - * git://host/repo[branch] - * ssh://host/repo[branch] - * http://host/repo[branch] - * https://host/repo[branch] - * file://repo[branch] - */ -static struct git_repository *is_remote_git_repository(char *remote, const char *branch) -{ - char c, *localdir; - const char *p = remote; - - while ((c = *p++) >= 'a' && c <= 'z') - /* nothing */; - if (c != ':') - return NULL; - if (*p++ != '/' || *p++ != '/') - return NULL; - - /* Special-case "file://", since it's already local */ - if (!strncmp(remote, "file://", 7)) - remote += 7; - - /* - * Ok, we found "[a-z]*://", we've simplified the - * local repo case (because libgit2 is insanely slow - * for that), and we think we have a real "remote - * git" format. - * - * We now create the SHA1 hash of the whole thing, - * including the branch name. That will be our unique - * unique local repository name. - * - * NOTE! We will create a local repository per branch, - * because - * - * (a) libgit2 remote tracking branch support seems to - * be a bit lacking - * (b) we'll actually check the branch out so that we - * can do merges etc too. - * - * so even if you have a single remote git repo with - * multiple branches for different people, the local - * caches will sadly force that to split into multiple - * individual repositories. - */ - - /* - * next we need to make sure that any encoded username - * has been extracted from an https:// based URL - */ - if (!strncmp(remote, "https://", 8)) { - char *at = strchr(remote, '@'); - if (at) { - /* was this the @ that denotes an account? that means it was before the - * first '/' after the https:// - so let's find a '/' after that and compare */ - char *slash = strchr(remote + 8, '/'); - if (slash && slash > at) { - /* grab the part between "https://" and "@" as encoded email address - * (that's our username) and move the rest of the URL forward, remembering - * to copy the closing NUL as well */ - prefs.cloud_storage_email_encoded = strndup(remote + 8, at - remote - 8); - memmove(remote + 8, at + 1, strlen(at + 1) + 1); - } - } - } - localdir = get_local_dir(remote, branch); - if (!localdir) - return NULL; - - /* remember if the current git storage we are working on is our cloud storage - * this is used to create more user friendly error message and warnings */ - is_subsurface_cloud = strstr(remote, prefs.cloud_git_url) != NULL; - - return get_remote_repo(localdir, remote, branch); -} - -/* - * If it's not a git repo, return NULL. Be very conservative. - */ -struct git_repository *is_git_repository(const char *filename, const char **branchp, const char **remote, bool dry_run) -{ - int flen, blen, ret; - int offset = 1; - struct stat st; - git_repository *repo; - char *loc, *branch; - - flen = strlen(filename); - if (!flen || filename[--flen] != ']') - return NULL; - - /* Find the matching '[' */ - blen = 0; - while (flen && filename[--flen] != '[') - blen++; - - /* Ignore slashes at the end of the repo name */ - while (flen && filename[flen-1] == '/') { - flen--; - offset++; - } - - if (!flen) - return NULL; - - /* - * This is the "point of no return": the name matches - * the git repository name rules, and we will no longer - * return NULL. - * - * We will either return "dummy_git_repository" and the - * branch pointer will have the _whole_ filename in it, - * or we will return a real git repository with the - * branch pointer being filled in with just the branch - * name. - * - * The actual git reading/writing routines can use this - * to generate proper error messages. - */ - *branchp = filename; - loc = format_string("%.*s", flen, filename); - if (!loc) - return dummy_git_repository; - - branch = format_string("%.*s", blen, filename + flen + offset); - if (!branch) { - free(loc); - return dummy_git_repository; - } - - if (dry_run) { - *branchp = branch; - *remote = loc; - return dummy_git_repository; - } - repo = is_remote_git_repository(loc, branch); - if (repo) { - if (remote) - *remote = loc; - else - free(loc); - *branchp = branch; - return repo; - } - - if (stat(loc, &st) < 0 || !S_ISDIR(st.st_mode)) { - free(loc); - free(branch); - return dummy_git_repository; - } - - ret = git_repository_open(&repo, loc); - free(loc); - if (ret < 0) { - free(branch); - return dummy_git_repository; - } - if (remote) - *remote = NULL; - *branchp = branch; - return repo; -} diff --git a/subsurface-core/git-access.h b/subsurface-core/git-access.h deleted file mode 100644 index 3a0c5160a..000000000 --- a/subsurface-core/git-access.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef GITACCESS_H -#define GITACCESS_H - -#include "git2.h" - -#ifdef __cplusplus -extern "C" { -#else -#include <stdbool.h> -#endif - -enum remote_transport { RT_OTHER, RT_HTTPS, RT_SSH }; - -struct git_oid; -struct git_repository; -#define dummy_git_repository ((git_repository *)3ul) /* Random bogus pointer, not NULL */ -extern struct git_repository *is_git_repository(const char *filename, const char **branchp, const char **remote, bool dry_run); -extern int sync_with_remote(struct git_repository *repo, const char *remote, const char *branch, enum remote_transport rt); -extern int git_save_dives(struct git_repository *, const char *, const char *remote, bool select_only); -extern int git_load_dives(struct git_repository *, const char *); -extern const char *get_sha(git_repository *repo, const char *branch); -extern int do_git_save(git_repository *repo, const char *branch, const char *remote, bool select_only, bool create_empty); -extern const char *saved_git_id; -extern void clear_git_id(void); -extern void set_git_id(const struct git_oid *); -void set_git_update_cb(int (*)(int, const char *)); -int git_storage_update_progress(int percent, const char *text); -char *get_local_dir(const char *remote, const char *branch); - -extern int last_git_storage_update_val; - -#ifdef __cplusplus -} -#endif -#endif // GITACCESS_H - diff --git a/subsurface-core/gpslocation.cpp b/subsurface-core/gpslocation.cpp deleted file mode 100644 index 075b1c046..000000000 --- a/subsurface-core/gpslocation.cpp +++ /dev/null @@ -1,606 +0,0 @@ -#include "gpslocation.h" -#include "gpslistmodel.h" -#include "pref.h" -#include "dive.h" -#include "helpers.h" -#include <time.h> -#include <unistd.h> -#include <QDebug> -#include <QVariant> -#include <QUrlQuery> -#include <QApplication> -#include <QTimer> -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> - -#define GPS_FIX_ADD_URL "http://api.subsurface-divelog.org/api/dive/add/" -#define GPS_FIX_DELETE_URL "http://api.subsurface-divelog.org/api/dive/delete/" -#define GPS_FIX_DOWNLOAD_URL "http://api.subsurface-divelog.org/api/dive/get/" -#define GET_WEBSERVICE_UID_URL "https://cloud.subsurface-divelog.org/webuserid/" - -GpsLocation *GpsLocation::m_Instance = NULL; - -GpsLocation::GpsLocation(void (*showMsgCB)(const char *), QObject *parent) : QObject(parent) -{ - Q_ASSERT_X(m_Instance == NULL, "GpsLocation", "GpsLocation recreated"); - m_Instance = this; - m_GpsSource = 0; - waitingForPosition = false; - showMessageCB = showMsgCB; - // create a QSettings object that's separate from the main application settings - geoSettings = new QSettings(QSettings::NativeFormat, QSettings::UserScope, - QString("org.subsurfacedivelog"), QString("subsurfacelocation"), this); -#ifdef SUBSURFACE_MOBILE - if (hasLocationsSource()) - status(QString("Found GPS with positioning methods %1").arg(QString::number(m_GpsSource->supportedPositioningMethods(), 16))); -#endif - userAgent = getUserAgent(); - loadFromStorage(); -} - -GpsLocation *GpsLocation::instance() -{ - Q_ASSERT(m_Instance != NULL); - - return m_Instance; -} - -GpsLocation::~GpsLocation() -{ - m_Instance = NULL; -} - -QGeoPositionInfoSource *GpsLocation::getGpsSource() -{ - if (!m_GpsSource) { - m_GpsSource = QGeoPositionInfoSource::createDefaultSource(this); - if (m_GpsSource != 0) { -#ifndef SUBSURFACE_MOBILE - if (verbose) -#endif - status(QString("Created position source %1").arg(m_GpsSource->sourceName())); - connect(m_GpsSource, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(newPosition(QGeoPositionInfo))); - connect(m_GpsSource, SIGNAL(updateTimeout()), this, SLOT(updateTimeout())); - m_GpsSource->setUpdateInterval(5 * 60 * 1000); // 5 minutes so the device doesn't drain the battery - } else { -#ifdef SUBSURFACE_MOBILE - status("don't have GPS source"); -#endif - } - } - return m_GpsSource; -} - -bool GpsLocation::hasLocationsSource() -{ - QGeoPositionInfoSource *gpsSource = getGpsSource(); - return gpsSource != 0 && (gpsSource->supportedPositioningMethods() & QGeoPositionInfoSource::SatellitePositioningMethods); -} - -void GpsLocation::serviceEnable(bool toggle) -{ - QGeoPositionInfoSource *gpsSource = getGpsSource(); - if (!gpsSource) { - if (toggle) - status("Can't start location service, no location source available"); - return; - } - if (toggle) { - gpsSource->startUpdates(); - status(QString("Starting Subsurface GPS service with update interval %1").arg(gpsSource->updateInterval())); - } else { - gpsSource->stopUpdates(); - status("Stopping Subsurface GPS service"); - } -} - -QString GpsLocation::currentPosition() -{ - if (!hasLocationsSource()) - return tr("Unknown GPS location"); - if (m_trackers.count() && m_trackers.lastKey() + 300 >= QDateTime::currentMSecsSinceEpoch() / 1000) { - // we can simply use the last position that we tracked - gpsTracker gt = m_trackers.last(); - QString gpsString = printGPSCoords(gt.latitude.udeg, gt.longitude.udeg); - qDebug() << "returning last position" << gpsString; - return gpsString; - } - qDebug() << "requesting new GPS position"; - m_GpsSource->requestUpdate(); - // ok, we need to get the current position and somehow in the callback update the location in the QML UI - // punting right now - waitingForPosition = true; - return QString("waiting for the next gps location"); -} - -void GpsLocation::newPosition(QGeoPositionInfo pos) -{ - int64_t lastTime = 0; - QGeoCoordinate lastCoord; - int nr = m_trackers.count(); - if (nr) { - gpsTracker gt = m_trackers.last(); - lastCoord.setLatitude(gt.latitude.udeg / 1000000.0); - lastCoord.setLongitude(gt.longitude.udeg / 1000000.0); - lastTime = gt.when; - } - // if we are waiting for a position update or - // if we have no record stored or if at least the configured minimum - // time has passed or we moved at least the configured minimum distance - int64_t delta = (int64_t)pos.timestamp().toTime_t() + gettimezoneoffset() - lastTime; - if (!nr || waitingForPosition || delta > prefs.time_threshold || - lastCoord.distanceTo(pos.coordinate()) > prefs.distance_threshold) { - QString msg("received new position %1 after delta %2 threshold %3"); - status(qPrintable(msg.arg(pos.coordinate().toString()).arg(delta).arg(prefs.time_threshold))); - waitingForPosition = false; - gpsTracker gt; - gt.when = pos.timestamp().toTime_t(); - gt.when += gettimezoneoffset(gt.when); - gt.latitude.udeg = rint(pos.coordinate().latitude() * 1000000); - gt.longitude.udeg = rint(pos.coordinate().longitude() * 1000000); - addFixToStorage(gt); - } -} - -void GpsLocation::updateTimeout() -{ - status("request to get new position timed out"); -} - -void GpsLocation::status(QString msg) -{ - qDebug() << msg; - if (showMessageCB) - (*showMessageCB)(qPrintable(msg)); -} - -QString GpsLocation::getUserid(QString user, QString passwd) -{ - qDebug() << "called getUserid"; - QEventLoop loop; - QTimer timer; - timer.setSingleShot(true); - - QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); - QUrl url(GET_WEBSERVICE_UID_URL); - QString data; - data = user + " " + passwd; - QNetworkRequest request; - request.setUrl(url); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Accept", "text/html"); - request.setRawHeader("Content-type", "application/x-www-form-urlencoded"); - reply = manager->post(request, data.toUtf8()); - connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(getUseridError(QNetworkReply::NetworkError))); - timer.start(10000); - loop.exec(); - if (timer.isActive()) { - timer.stop(); - if (reply->error() == QNetworkReply::NoError) { - QString result = reply->readAll(); - status(QString("received ") + result); - result.remove("WebserviceID:"); - reply->deleteLater(); - return result; - } - } else { - status("Getting Web service ID timed out"); - } - reply->deleteLater(); - return QString(); -} - -int GpsLocation::getGpsNum() const -{ - return m_trackers.count(); -} - -static void copy_gps_location(struct gpsTracker &gps, struct dive *d) -{ - struct dive_site *ds = get_dive_site_by_uuid(d->dive_site_uuid); - if (!ds) { - d->dive_site_uuid = create_dive_site(qPrintable(gps.name), gps.when); - ds = get_dive_site_by_uuid(d->dive_site_uuid); - } - ds->latitude = gps.latitude; - ds->longitude = gps.longitude; -} - -#define SAME_GROUP 6 * 3600 /* six hours */ -bool GpsLocation::applyLocations() -{ - int i; - bool changed = false; - int last = 0; - int cnt = m_trackers.count(); - if (cnt == 0) - return false; - - // create a table with the GPS information - QList<struct gpsTracker> gpsTable = m_trackers.values(); - - // now walk the dive table and see if we can fill in missing gps data - struct dive *d; - for_each_dive(i, d) { - if (dive_has_gps_location(d)) - continue; - for (int j = last; j < cnt; j++) { - if (time_during_dive_with_offset(d, gpsTable[j].when, SAME_GROUP)) { - if (verbose) - qDebug() << "processing gpsFix @" << get_dive_date_string(gpsTable[j].when) << - "which is withing six hours of dive from" << - get_dive_date_string(d->when) << "until" << - get_dive_date_string(d->when + d->duration.seconds); - /* - * If position is fixed during dive. This is the good one. - * Asign and mark position, and end gps_location loop - */ - if (time_during_dive_with_offset(d, gpsTable[j].when, 0)) { - if (verbose) - qDebug() << "gpsFix is during the dive, pick that one"; - copy_gps_location(gpsTable[j], d); - changed = true; - last = j; - break; - } else { - /* - * If it is not, check if there are more position fixes in SAME_GROUP range - */ - if (j + 1 < cnt && time_during_dive_with_offset(d, gpsTable[j+1].when, SAME_GROUP)) { - if (verbose) - qDebug() << "look at the next gps fix @" << get_dive_date_string(gpsTable[j+1].when); - /* first let's test if this one is during the dive */ - if (time_during_dive_with_offset(d, gpsTable[j+1].when, 0)) { - if (verbose) - qDebug() << "which is during the dive, pick that one"; - copy_gps_location(gpsTable[j+1], d); - changed = true; - last = j + 1; - break; - } - /* we know the gps fixes are sorted; if they are both before the dive, ignore the first, - * if theay are both after the dive, take the first, - * if the first is before and the second is after, take the closer one */ - if (gpsTable[j+1].when < d->when) { - if (verbose) - qDebug() << "which is closer to the start of the dive, do continue with that"; - continue; - } else if (gpsTable[j].when > d->when + d->duration.seconds) { - if (verbose) - qDebug() << "which is even later after the end of the dive, so pick the previous one"; - copy_gps_location(gpsTable[j], d); - changed = true; - last = j; - break; - } else { - /* ok, gpsFix is before, nextgpsFix is after */ - if (d->when - gpsTable[j].when <= gpsTable[j+1].when - (d->when + d->duration.seconds)) { - if (verbose) - qDebug() << "pick the one before as it's closer to the start"; - copy_gps_location(gpsTable[j], d); - changed = true; - last = j; - break; - } else { - if (verbose) - qDebug() << "pick the one after as it's closer to the start"; - copy_gps_location(gpsTable[j+1], d); - changed = true; - last = j + 1; - break; - } - } - /* - * If no more positions in range, the actual is the one. Asign, mark and end loop. - */ - } else { - if (verbose) - qDebug() << "which seems to be the best one for this dive, so pick it"; - copy_gps_location(gpsTable[j], d); - changed = true; - last = j; - break; - } - } - } else { - /* If position is out of SAME_GROUP range and in the future, mark position for - * next dive iteration and end the gps_location loop - */ - if (gpsTable[j].when >= d->when + d->duration.seconds + SAME_GROUP) { - last = j; - break; - } - } - - } - } - if (changed) - mark_divelist_changed(true); - return changed; -} - -QMap<qint64, gpsTracker> GpsLocation::currentGPSInfo() const -{ - return m_trackers; -} - -void GpsLocation::loadFromStorage() -{ - int nr = geoSettings->value(QString("count")).toInt(); - for (int i = 0; i < nr; i++) { - struct gpsTracker gt; - gt.when = geoSettings->value(QString("gpsFix%1_time").arg(i)).toLongLong(); - gt.latitude.udeg = geoSettings->value(QString("gpsFix%1_lat").arg(i)).toInt(); - gt.longitude.udeg = geoSettings->value(QString("gpsFix%1_lon").arg(i)).toInt(); - gt.name = geoSettings->value(QString("gpsFix%1_name").arg(i)).toString(); - gt.idx = i; - m_trackers.insert(gt.when, gt); - } -} - -void GpsLocation::replaceFixToStorage(gpsTracker >) -{ - if (!m_trackers.keys().contains(gt.when)) { - addFixToStorage(gt); - return; - } - gpsTracker replacedTracker = m_trackers.value(gt.when); - geoSettings->setValue(QString("gpsFix%1_time").arg(replacedTracker.idx), gt.when); - geoSettings->setValue(QString("gpsFix%1_lat").arg(replacedTracker.idx), gt.latitude.udeg); - geoSettings->setValue(QString("gpsFix%1_lon").arg(replacedTracker.idx), gt.longitude.udeg); - geoSettings->setValue(QString("gpsFix%1_name").arg(replacedTracker.idx), gt.name); - replacedTracker.latitude = gt.latitude; - replacedTracker.longitude = gt.longitude; - replacedTracker.name = gt.name; -} - -void GpsLocation::addFixToStorage(gpsTracker >) -{ - int nr = m_trackers.count(); - geoSettings->setValue("count", nr + 1); - geoSettings->setValue(QString("gpsFix%1_time").arg(nr), gt.when); - geoSettings->setValue(QString("gpsFix%1_lat").arg(nr), gt.latitude.udeg); - geoSettings->setValue(QString("gpsFix%1_lon").arg(nr), gt.longitude.udeg); - geoSettings->setValue(QString("gpsFix%1_name").arg(nr), gt.name); - gt.idx = nr; - geoSettings->sync(); - m_trackers.insert(gt.when, gt); -} - -void GpsLocation::deleteFixFromStorage(gpsTracker >) -{ - qint64 when = gt.when; - int cnt = m_trackers.count(); - if (cnt == 0 || !m_trackers.keys().contains(when)) { - qDebug() << "no gps fix with timestamp" << when; - return; - } - if (geoSettings->value(QString("gpsFix%1_time").arg(gt.idx)).toLongLong() != when) { - qDebug() << "uh oh - index for tracker has gotten out of sync..."; - return; - } - if (cnt > 1) { - // now we move the last tracker into that spot (so our settings stay consecutive) - // and delete the last settings entry - when = geoSettings->value(QString("gpsFix%1_time").arg(cnt - 1)).toLongLong(); - struct gpsTracker movedTracker = m_trackers.value(when); - movedTracker.idx = gt.idx; - m_trackers.remove(movedTracker.when); - m_trackers.insert(movedTracker.when, movedTracker); - geoSettings->setValue(QString("gpsFix%1_time").arg(movedTracker.idx), when); - geoSettings->setValue(QString("gpsFix%1_lat").arg(movedTracker.idx), movedTracker.latitude.udeg); - geoSettings->setValue(QString("gpsFix%1_lon").arg(movedTracker.idx), movedTracker.longitude.udeg); - geoSettings->setValue(QString("gpsFix%1_name").arg(movedTracker.idx), movedTracker.name); - geoSettings->remove(QString("gpsFix%1_lat").arg(cnt - 1)); - geoSettings->remove(QString("gpsFix%1_lon").arg(cnt - 1)); - geoSettings->remove(QString("gpsFix%1_time").arg(cnt - 1)); - geoSettings->remove(QString("gpsFix%1_name").arg(cnt - 1)); - } - geoSettings->setValue("count", cnt - 1); - geoSettings->sync(); - m_trackers.remove(gt.when); -} - -void GpsLocation::deleteGpsFix(qint64 when) -{ - struct gpsTracker defaultTracker; - defaultTracker.when = 0; - struct gpsTracker deletedTracker = m_trackers.value(when, defaultTracker); - if (deletedTracker.when != when) { - qDebug() << "can't find tracker for timestamp" << when; - return; - } - deleteFixFromStorage(deletedTracker); - m_deletedTrackers.append(deletedTracker); -} - -void GpsLocation::clearGpsData() -{ - m_trackers.clear(); - geoSettings->clear(); - geoSettings->sync(); -} - -void GpsLocation::postError(QNetworkReply::NetworkError error) -{ - Q_UNUSED(error); - status(QString("error when sending a GPS fix: %1").arg(reply->errorString())); -} - -void GpsLocation::getUseridError(QNetworkReply::NetworkError error) -{ - Q_UNUSED(error); - status(QString("error when retrieving Subsurface webservice user id: %1").arg(reply->errorString())); -} - -void GpsLocation::deleteFixesFromServer() -{ - QEventLoop loop; - QTimer timer; - timer.setSingleShot(true); - - QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); - QUrl url(GPS_FIX_DELETE_URL); - QList<qint64> keys = m_trackers.keys(); - while (!m_deletedTrackers.isEmpty()) { - gpsTracker gt = m_deletedTrackers.takeFirst(); - QDateTime dt; - QUrlQuery data; - dt.setTime_t(gt.when - gettimezoneoffset(gt.when)); - data.addQueryItem("login", prefs.userid); - data.addQueryItem("dive_date", dt.toString("yyyy-MM-dd")); - data.addQueryItem("dive_time", dt.toString("hh:mm")); - status(data.toString(QUrl::FullyEncoded).toUtf8()); - QNetworkRequest request; - request.setUrl(url); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Accept", "text/json"); - request.setRawHeader("Content-type", "application/x-www-form-urlencoded"); - reply = manager->post(request, data.toString(QUrl::FullyEncoded).toUtf8()); - connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(postError(QNetworkReply::NetworkError))); - timer.start(10000); - loop.exec(); - if (timer.isActive()) { - timer.stop(); - if (reply->error() != QNetworkReply::NoError) { - QString response = reply->readAll(); - status(QString("Server response:") + reply->readAll()); - } - } else { - status("Deleting on the server timed out - try again later"); - m_deletedTrackers.prepend(gt); - break; - } - reply->deleteLater(); - status(QString("completed deleting gps fix %1 - response: ").arg(gt.idx) + reply->readAll()); - } -} - -void GpsLocation::uploadToServer() -{ - // we want to do this one at a time (the server prefers that) - QEventLoop loop; - QTimer timer; - timer.setSingleShot(true); - - QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); - QUrl url(GPS_FIX_ADD_URL); - Q_FOREACH(qint64 key, m_trackers.keys()) { - struct gpsTracker gt = m_trackers.value(key); - QDateTime dt; - QUrlQuery data; - dt.setTime_t(gt.when - gettimezoneoffset(gt.when)); - data.addQueryItem("login", prefs.userid); - data.addQueryItem("dive_date", dt.toString("yyyy-MM-dd")); - data.addQueryItem("dive_time", dt.toString("hh:mm")); - data.addQueryItem("dive_latitude", QString::number(gt.latitude.udeg / 1000000.0, 'f', 9)); - data.addQueryItem("dive_longitude", QString::number(gt.longitude.udeg / 1000000.0, 'f', 9)); - if (gt.name.isEmpty()) - gt.name = "Auto-created dive"; - data.addQueryItem("dive_name", gt.name); - status(data.toString(QUrl::FullyEncoded).toUtf8()); - QNetworkRequest request; - request.setUrl(url); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Accept", "text/json"); - request.setRawHeader("Content-type", "application/x-www-form-urlencoded"); - reply = manager->post(request, data.toString(QUrl::FullyEncoded).toUtf8()); - connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - // somehoe I cannot get this to work with the new connect syntax: - // connect(reply, &QNetworkReply::error, this, &GpsLocation::postError); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(postError(QNetworkReply::NetworkError))); - timer.start(10000); - loop.exec(); - if (timer.isActive()) { - timer.stop(); - if (reply->error() != QNetworkReply::NoError) { - QString response = reply->readAll(); - if (!response.contains("Duplicate entry")) { - status(QString("Server response:") + reply->readAll()); - break; - } - } - } else { - status("Uploading to server timed out"); - break; - } - reply->deleteLater(); - status(QString("completed sending gps fix %1 - response: ").arg(gt.idx) + reply->readAll()); - } - // and now remove the ones that were locally deleted - deleteFixesFromServer(); -} - -void GpsLocation::downloadFromServer() -{ - QEventLoop loop; - QTimer timer; - timer.setSingleShot(true); - QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); - QUrl url(QString(GPS_FIX_DOWNLOAD_URL "?login=%1").arg(prefs.userid)); - QNetworkRequest request; - request.setUrl(url); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Accept", "text/json"); - request.setRawHeader("Content-type", "text/html"); - qDebug() << "downloadFromServer accessing" << url; - reply = manager->get(request); - connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(getUseridError(QNetworkReply::NetworkError))); - timer.start(10000); - loop.exec(); - if (timer.isActive()) { - timer.stop(); - if (!reply->error()) { - QString response = reply->readAll(); - QJsonDocument json = QJsonDocument::fromJson(response.toLocal8Bit()); - QJsonObject object = json.object(); - if (object.value("download").toString() != "ok") { - qDebug() << "problems downloading GPS fixes"; - return; - } - qDebug() << "already have" << m_trackers.count() << "GPS fixes"; - QJsonArray downloadedFixes = object.value("dives").toArray(); - qDebug() << downloadedFixes.count() << "GPS fixes downloaded"; - for (int i = 0; i < downloadedFixes.count(); i++) { - QJsonObject fix = downloadedFixes[i].toObject(); - QString date = fix.value("date").toString(); - QString time = fix.value("time").toString(); - QString name = fix.value("name").toString(); - QString latitude = fix.value("latitude").toString(); - QString longitude = fix.value("longitude").toString(); - QDateTime timestamp = QDateTime::fromString(date + " " + time, "yyyy-M-d hh:m:s"); - - struct gpsTracker gt; - gt.when = timestamp.toMSecsSinceEpoch() / 1000 + gettimezoneoffset(timestamp.toMSecsSinceEpoch() / 1000); - gt.latitude.udeg = latitude.toDouble() * 1000000; - gt.longitude.udeg = longitude.toDouble() * 1000000; - gt.name = name; - // add this GPS fix to the QMap and the settings (remove existing fix at the same timestamp first) - if (m_trackers.keys().contains(gt.when)) { - qDebug() << "already have a fix at time stamp" << gt.when; - replaceFixToStorage(gt); - } else { - addFixToStorage(gt); - } - } - } else { - qDebug() << "network error" << reply->error() << reply->errorString() << reply->readAll(); - } - } else { - qDebug() << "download timed out"; - status("Download from server timed out"); - } - reply->deleteLater(); -} diff --git a/subsurface-core/gpslocation.h b/subsurface-core/gpslocation.h deleted file mode 100644 index 82b51a291..000000000 --- a/subsurface-core/gpslocation.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef GPSLOCATION_H -#define GPSLOCATION_H - -#include "units.h" -#include <QObject> -#include <QGeoCoordinate> -#include <QGeoPositionInfoSource> -#include <QGeoPositionInfo> -#include <QSettings> -#include <QNetworkReply> -#include <QMap> - -struct gpsTracker { - degrees_t latitude; - degrees_t longitude; - qint64 when; - QString name; - int idx; -}; - -class GpsLocation : QObject { - Q_OBJECT -public: - GpsLocation(void (*showMsgCB)(const char *msg), QObject *parent); - ~GpsLocation(); - static GpsLocation *instance(); - bool applyLocations(); - int getGpsNum() const; - QString getUserid(QString user, QString passwd); - bool hasLocationsSource(); - QString currentPosition(); - - QMap<qint64, gpsTracker> currentGPSInfo() const; - -private: - QGeoPositionInfo lastPos; - QGeoPositionInfoSource *getGpsSource(); - QGeoPositionInfoSource *m_GpsSource; - void status(QString msg); - QSettings *geoSettings; - QNetworkReply *reply; - QString userAgent; - void (*showMessageCB)(const char *msg); - static GpsLocation *m_Instance; - bool waitingForPosition; - QMap<qint64, gpsTracker> m_trackers; - QList<gpsTracker> m_deletedTrackers; - void loadFromStorage(); - void addFixToStorage(gpsTracker >); - void replaceFixToStorage(gpsTracker >); - void deleteFixFromStorage(gpsTracker >); - void deleteFixesFromServer(); - -public slots: - void serviceEnable(bool toggle); - void newPosition(QGeoPositionInfo pos); - void updateTimeout(); - void uploadToServer(); - void downloadFromServer(); - void postError(QNetworkReply::NetworkError error); - void getUseridError(QNetworkReply::NetworkError error); - void clearGpsData(); - void deleteGpsFix(qint64 when); -}; - -#endif // GPSLOCATION_H diff --git a/subsurface-core/helpers.h b/subsurface-core/helpers.h deleted file mode 100644 index f88da015c..000000000 --- a/subsurface-core/helpers.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * helpers.h - * - * header file for random helper functions of Subsurface - * - */ -#ifndef HELPERS_H -#define HELPERS_H - -#include <QString> -#include "dive.h" -#include "qthelper.h" - -QString get_depth_string(depth_t depth, bool showunit = false, bool showdecimal = true); -QString get_depth_string(int mm, bool showunit = false, bool showdecimal = true); -QString get_depth_unit(); -QString get_weight_string(weight_t weight, bool showunit = false); -QString get_weight_unit(); -QString get_cylinder_used_gas_string(cylinder_t *cyl, bool showunit = false); -QString get_temperature_string(temperature_t temp, bool showunit = false); -QString get_temp_unit(); -QString get_volume_string(volume_t volume, bool showunit = false); -QString get_volume_unit(); -QString get_pressure_string(pressure_t pressure, bool showunit = false); -QString get_pressure_unit(); -void set_default_dive_computer(const char *vendor, const char *product); -void set_default_dive_computer_device(const char *name); -void set_default_dive_computer_download_mode(int downloadMode); -QString getSubsurfaceDataPath(QString folderToFind); -QString getPrintingTemplatePathUser(); -QString getPrintingTemplatePathBundle(); -void copyPath(QString src, QString dst); -extern const QString get_dc_nickname(const char *model, uint32_t deviceid); -int gettimezoneoffset(timestamp_t when = 0); -int parseLengthToMm(const QString &text); -int parseTemperatureToMkelvin(const QString &text); -int parseWeightToGrams(const QString &text); -int parsePressureToMbar(const QString &text); -int parseGasMixO2(const QString &text); -int parseGasMixHE(const QString &text); -QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText); -QString get_dive_date_string(timestamp_t when); -QString get_short_dive_date_string(timestamp_t when); -bool is_same_day (timestamp_t trip_when, timestamp_t dive_when); -QString get_trip_date_string(timestamp_t when, int nr, bool getday); -QString uiLanguage(QLocale *callerLoc); -QLocale getLocale(); -void selectedDivesGasUsed(QVector<QPair<QString, int> > &gasUsed); -QString getUserAgent(); - -#if defined __APPLE__ -#define TITLE_OR_TEXT(_t, _m) "", _t + "\n" + _m -#else -#define TITLE_OR_TEXT(_t, _m) _t, _m -#endif -#endif // HELPERS_H diff --git a/subsurface-core/imagedownloader.cpp b/subsurface-core/imagedownloader.cpp deleted file mode 100644 index daa49eadf..000000000 --- a/subsurface-core/imagedownloader.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "dive.h" -#include "metrics.h" -#include "divelist.h" -#include "qthelper.h" -#include "imagedownloader.h" -#include <unistd.h> - -#include <QtConcurrent> - -QUrl cloudImageURL(const char *hash) -{ - return QUrl::fromUserInput(QString("https://cloud.subsurface-divelog.org/images/").append(hash)); -} - -ImageDownloader::ImageDownloader(struct picture *pic) -{ - picture = pic; -} - -ImageDownloader::~ImageDownloader() -{ - picture_free(picture); -} - -void ImageDownloader::load(bool fromHash){ - QUrl url; - loadFromHash = fromHash; - if(fromHash) - url = cloudImageURL(picture->hash); - else - url = QUrl::fromUserInput(QString(picture->filename)); - if (url.isValid()) { - QEventLoop loop; - QNetworkRequest request(url); - connect(&manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(saveImage(QNetworkReply *))); - QNetworkReply *reply = manager.get(request); - while (reply->isRunning()) { - loop.processEvents(); - sleep(1); - } - } -} - -void ImageDownloader::saveImage(QNetworkReply *reply) -{ - QByteArray imageData = reply->readAll(); - QImage image = QImage(); - image.loadFromData(imageData); - if (image.isNull()) { - if (loadFromHash) - load(false); - return; - } - QCryptographicHash hash(QCryptographicHash::Sha1); - hash.addData(imageData); - QString path = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first(); - QDir dir(path); - if (!dir.exists()) - dir.mkpath(path); - QFile imageFile(path.append("/").append(hash.result().toHex())); - if (imageFile.open(QIODevice::WriteOnly)) { - QDataStream stream(&imageFile); - stream.writeRawData(imageData.data(), imageData.length()); - imageFile.waitForBytesWritten(-1); - imageFile.close(); - add_hash(imageFile.fileName(), hash.result()); - learnHash(picture, hash.result()); - } - reply->manager()->deleteLater(); - reply->deleteLater(); - // This should be called to make the picture actually show. - // Problem is DivePictureModel is not in subsurface-core. - // Nevertheless, the image shows when the dive is selected the next time. - // DivePictureModel::instance()->updateDivePictures(); - -} - -void loadPicture(struct picture *picture, bool fromHash) -{ - ImageDownloader download(picture); - download.load(fromHash); -} - -SHashedImage::SHashedImage(struct picture *picture) : QImage() -{ - QUrl url = QUrl::fromUserInput(localFilePath(QString(picture->filename))); - if(url.isLocalFile()) - load(url.toLocalFile()); - if (isNull()) { - // This did not load anything. Let's try to get the image from other sources - // Let's try to load it locally via its hash - QString filename = fileFromHash(picture->hash); - if (filename.isNull()) { - // That didn't produce a local filename. - // Try the cloud server - QtConcurrent::run(loadPicture, clone_picture(picture), true); - } else { - // Load locally from translated file name - load(filename); - if (!isNull()) { - // Make sure the hash still matches the image file - QtConcurrent::run(updateHash, clone_picture(picture)); - } else { - // Interpret filename as URL - QtConcurrent::run(loadPicture, clone_picture(picture), false); - } - } - } else { - // We loaded successfully. Now, make sure hash is up to date. - QtConcurrent::run(hashPicture, clone_picture(picture)); - } -} - diff --git a/subsurface-core/imagedownloader.h b/subsurface-core/imagedownloader.h deleted file mode 100644 index f4e3df875..000000000 --- a/subsurface-core/imagedownloader.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef IMAGEDOWNLOADER_H -#define IMAGEDOWNLOADER_H - -#include <QImage> -#include <QFuture> -#include <QNetworkReply> - -typedef QPair<QString, QByteArray> SHashedFilename; - -extern QUrl cloudImageURL(const char *hash); - - -class ImageDownloader : public QObject { - Q_OBJECT; -public: - ImageDownloader(struct picture *picture); - ~ImageDownloader(); - void load(bool fromHash); - -private: - struct picture *picture; - QNetworkAccessManager manager; - bool loadFromHash; - -private slots: - void saveImage(QNetworkReply *reply); -}; - -class SHashedImage : public QImage { -public: - SHashedImage(struct picture *picture); -}; - -#endif // IMAGEDOWNLOADER_H diff --git a/subsurface-core/isocialnetworkintegration.cpp b/subsurface-core/isocialnetworkintegration.cpp deleted file mode 100644 index eb1e82a49..000000000 --- a/subsurface-core/isocialnetworkintegration.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "isocialnetworkintegration.h" - -//Hack for moc. -ISocialNetworkIntegration::ISocialNetworkIntegration(QObject* parent) : QObject(parent) -{ -} diff --git a/subsurface-core/isocialnetworkintegration.h b/subsurface-core/isocialnetworkintegration.h deleted file mode 100644 index 70ea3d9ab..000000000 --- a/subsurface-core/isocialnetworkintegration.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef ISOCIALNETWORKINTEGRATION_H -#define ISOCIALNETWORKINTEGRATION_H - -#include <QtPlugin> - -/* This Interface represents a Plugin for Social Network integration, - * with it you may be able to create plugins for facebook, instagram, - * twitpic, google plus and any other thing you may imagine. - * - * We bundle facebook integration as an example. - */ - -class ISocialNetworkIntegration : public QObject { - Q_OBJECT -public: - ISocialNetworkIntegration(QObject* parent = 0); - - /*! - * @name socialNetworkName - * @brief The name of this social network - * @return The name of this social network - * - * The name of this social network will be used to populate the Menu to toggle states - * between connected/disconnected, and also submit stuff to it. - */ - virtual QString socialNetworkName() const = 0; - - /*! - * @name socialNetworkIcon - * @brief The icon of this social network - * @return The icon of this social network - * - * The icon of this social network will be used to populate the menu, and can also be - * used on a toolbar if requested. - */ - virtual QString socialNetworkIcon() const = 0; - - /*! - * @name isConnected - * @brief returns true if connected to this social network, false otherwise - * @return true if connected to this social network, false otherwise - */ - virtual bool isConnected() = 0; - - /*! - * @name requestLogin - * @brief try to login on this social network. - * - * Try to login on this social network. All widget implementation that - * manages login should be done inside this function. - */ - virtual void requestLogin() = 0; - - /*! - * @name requestLogoff - * @brief tries to logoff from this social network - * - * Try to logoff from this social network. - */ - virtual void requestLogoff() = 0; - - /*! - * @name uploadCurrentDive - * @brief send the current dive info to the Social Network - * - * Should format all the options and pixmaps from the current dive - * to update to the social network. All widget stuff related to sendint - * dive information should be executed inside this function. - */ - virtual void requestUpload() = 0; -}; - -#endif
\ No newline at end of file diff --git a/subsurface-core/libdivecomputer.c b/subsurface-core/libdivecomputer.c deleted file mode 100644 index 549b894ce..000000000 --- a/subsurface-core/libdivecomputer.c +++ /dev/null @@ -1,1081 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include <stdio.h> -#include <unistd.h> -#include <inttypes.h> -#include <string.h> -#include "gettext.h" -#include "dive.h" -#include "device.h" -#include "divelist.h" -#include "display.h" - -#include <libdivecomputer/uwatec.h> -#include <libdivecomputer/hw.h> -#include <libdivecomputer/version.h> -#include "libdivecomputer.h" - -/* Christ. Libdivecomputer has the worst configuration system ever. */ -#ifdef HW_FROG_H -#define NOT_FROG , 0 -#define LIBDIVECOMPUTER_SUPPORTS_FROG -#else -#define NOT_FROG -#endif - -char *dumpfile_name; -char *logfile_name; -const char *progress_bar_text = ""; -double progress_bar_fraction = 0.0; - -static int stoptime, stopdepth, ndl, po2, cns; -static bool in_deco, first_temp_is_air; - -/* - * Directly taken from libdivecomputer's examples/common.c to improve - * the error messages resulting from libdc's return codes - */ -const char *errmsg (dc_status_t rc) -{ - switch (rc) { - case DC_STATUS_SUCCESS: - return "Success"; - case DC_STATUS_UNSUPPORTED: - return "Unsupported operation"; - case DC_STATUS_INVALIDARGS: - return "Invalid arguments"; - case DC_STATUS_NOMEMORY: - return "Out of memory"; - case DC_STATUS_NODEVICE: - return "No device found"; - case DC_STATUS_NOACCESS: - return "Access denied"; - case DC_STATUS_IO: - return "Input/output error"; - case DC_STATUS_TIMEOUT: - return "Timeout"; - case DC_STATUS_PROTOCOL: - return "Protocol error"; - case DC_STATUS_DATAFORMAT: - return "Data format error"; - case DC_STATUS_CANCELLED: - return "Cancelled"; - default: - return "Unknown error"; - } -} - -static dc_status_t create_parser(device_data_t *devdata, dc_parser_t **parser) -{ - return dc_parser_new(parser, devdata->device); -} - -static int parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_parser_t *parser, unsigned int ngases) -{ - static bool shown_warning = false; - unsigned int i; - int rc; - -#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) - unsigned int ntanks = 0; - rc = dc_parser_get_field(parser, DC_FIELD_TANK_COUNT, 0, &ntanks); - if (rc == DC_STATUS_SUCCESS) { - if (ntanks && ntanks != ngases) { - shown_warning = true; - report_error("different number of gases (%d) and tanks (%d)", ngases, ntanks); - } - } - dc_tank_t tank = { 0 }; -#endif - - for (i = 0; i < ngases; i++) { - dc_gasmix_t gasmix = { 0 }; - int o2, he; - bool no_volume = true; - - rc = dc_parser_get_field(parser, DC_FIELD_GASMIX, i, &gasmix); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) - return rc; - - if (i >= MAX_CYLINDERS) - continue; - - o2 = rint(gasmix.oxygen * 1000); - he = rint(gasmix.helium * 1000); - - /* Ignore bogus data - libdivecomputer does some crazy stuff */ - if (o2 + he <= O2_IN_AIR || o2 > 1000) { - if (!shown_warning) { - shown_warning = true; - report_error("unlikely dive gas data from libdivecomputer: o2 = %d he = %d", o2, he); - } - o2 = 0; - } - if (he < 0 || o2 + he > 1000) { - if (!shown_warning) { - shown_warning = true; - report_error("unlikely dive gas data from libdivecomputer: o2 = %d he = %d", o2, he); - } - he = 0; - } - dive->cylinder[i].gasmix.o2.permille = o2; - dive->cylinder[i].gasmix.he.permille = he; - -#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) - tank.volume = 0.0; - if (i < ntanks) { - rc = dc_parser_get_field(parser, DC_FIELD_TANK, i, &tank); - if (rc == DC_STATUS_SUCCESS) { - if (tank.type == DC_TANKVOLUME_IMPERIAL) { - dive->cylinder[i].type.size.mliter = rint(tank.volume * 1000); - dive->cylinder[i].type.workingpressure.mbar = rint(tank.workpressure * 1000); - if (same_string(devdata->model, "Suunto EON Steel")) { - /* Suunto EON Steele gets this wrong. Badly. - * but on the plus side it only supports a few imperial sizes, - * so let's try and guess at least the most common ones. - * First, the pressures are off by a constant factor. WTF? - * Then we can round the wet sizes so we get to multiples of 10 - * for cuft sizes (as that's all that you can enter) */ - dive->cylinder[i].type.workingpressure.mbar *= 206.843 / 206.7; - char name_buffer[9]; - int rounded_size = ml_to_cuft(gas_volume(&dive->cylinder[i], - dive->cylinder[i].type.workingpressure)); - rounded_size = (int)((rounded_size + 5) / 10) * 10; - switch (dive->cylinder[i].type.workingpressure.mbar) { - case 206843: - snprintf(name_buffer, 9, "AL%d", rounded_size); - break; - case 234422: /* this is wrong - HP tanks tend to be 3440, but Suunto only allows 3400 */ - snprintf(name_buffer, 9, "HP%d", rounded_size); - break; - case 179263: - snprintf(name_buffer, 9, "LP+%d", rounded_size); - break; - case 165474: - snprintf(name_buffer, 9, "LP%d", rounded_size); - break; - default: - snprintf(name_buffer, 9, "%d cuft", rounded_size); - break; - } - dive->cylinder[i].type.description = copy_string(name_buffer); - dive->cylinder[i].type.size.mliter = cuft_to_l(rounded_size) * 1000 / - mbar_to_atm(dive->cylinder[i].type.workingpressure.mbar); - } - } else if (tank.type == DC_TANKVOLUME_METRIC) { - dive->cylinder[i].type.size.mliter = rint(tank.volume * 1000); - } - if (tank.gasmix != i) { // we don't handle this, yet - shown_warning = true; - report_error("gasmix %d for tank %d doesn't match", tank.gasmix, i); - } - } - } - if (!IS_FP_SAME(tank.volume, 0.0)) - no_volume = false; - - // this new API also gives us the beginning and end pressure for the tank - if (!IS_FP_SAME(tank.beginpressure, 0.0) && !IS_FP_SAME(tank.endpressure, 0.0)) { - dive->cylinder[i].start.mbar = tank.beginpressure * 1000; - dive->cylinder[i].end.mbar = tank.endpressure * 1000; - } -#endif - if (no_volume) { - /* for the first tank, if there is no tanksize available from the - * dive computer, fill in the default tank information (if set) */ - fill_default_cylinder(&dive->cylinder[i]); - } - /* whatever happens, make sure there is a name for the cylinder */ - if (same_string(dive->cylinder[i].type.description, "")) - dive->cylinder[i].type.description = strdup(translate("gettextFromC", "unknown")); - } - return DC_STATUS_SUCCESS; -} - -static void handle_event(struct divecomputer *dc, struct sample *sample, dc_sample_value_t value) -{ - int type, time; - /* we mark these for translation here, but we store the untranslated strings - * and only translate them when they are displayed on screen */ - static const char *events[] = { - QT_TRANSLATE_NOOP("gettextFromC", "none"), QT_TRANSLATE_NOOP("gettextFromC", "deco stop"), QT_TRANSLATE_NOOP("gettextFromC", "rbt"), QT_TRANSLATE_NOOP("gettextFromC", "ascent"), QT_TRANSLATE_NOOP("gettextFromC", "ceiling"), QT_TRANSLATE_NOOP("gettextFromC", "workload"), - QT_TRANSLATE_NOOP("gettextFromC", "transmitter"), QT_TRANSLATE_NOOP("gettextFromC", "violation"), QT_TRANSLATE_NOOP("gettextFromC", "bookmark"), QT_TRANSLATE_NOOP("gettextFromC", "surface"), QT_TRANSLATE_NOOP("gettextFromC", "safety stop"), - QT_TRANSLATE_NOOP("gettextFromC", "gaschange"), QT_TRANSLATE_NOOP("gettextFromC", "safety stop (voluntary)"), QT_TRANSLATE_NOOP("gettextFromC", "safety stop (mandatory)"), - QT_TRANSLATE_NOOP("gettextFromC", "deepstop"), QT_TRANSLATE_NOOP("gettextFromC", "ceiling (safety stop)"), QT_TRANSLATE_NOOP3("gettextFromC", "below floor", "event showing dive is below deco floor and adding deco time"), QT_TRANSLATE_NOOP("gettextFromC", "divetime"), - QT_TRANSLATE_NOOP("gettextFromC", "maxdepth"), QT_TRANSLATE_NOOP("gettextFromC", "OLF"), QT_TRANSLATE_NOOP("gettextFromC", "pOâ‚‚"), QT_TRANSLATE_NOOP("gettextFromC", "airtime"), QT_TRANSLATE_NOOP("gettextFromC", "rgbm"), QT_TRANSLATE_NOOP("gettextFromC", "heading"), - QT_TRANSLATE_NOOP("gettextFromC", "tissue level warning"), QT_TRANSLATE_NOOP("gettextFromC", "gaschange"), QT_TRANSLATE_NOOP("gettextFromC", "non stop time") - }; - const int nr_events = sizeof(events) / sizeof(const char *); - const char *name; - /* - * Just ignore surface events. They are pointless. What "surface" - * means depends on the dive computer (and possibly even settings - * in the dive computer). It does *not* necessarily mean "depth 0", - * so don't even turn it into that. - */ - if (value.event.type == SAMPLE_EVENT_SURFACE) - return; - - /* - * Other evens might be more interesting, but for now we just print them out. - */ - type = value.event.type; - name = QT_TRANSLATE_NOOP("gettextFromC", "invalid event number"); - if (type < nr_events) - name = events[type]; - - time = value.event.time; - if (sample) - time += sample->time.seconds; - - add_event(dc, time, type, value.event.flags, value.event.value, name); -} - -void -sample_cb(dc_sample_type_t type, dc_sample_value_t value, void *userdata) -{ - static unsigned int nsensor = 0; - struct divecomputer *dc = userdata; - struct sample *sample; - - /* - * We fill in the "previous" sample - except for DC_SAMPLE_TIME, - * which creates a new one. - */ - sample = dc->samples ? dc->sample + dc->samples - 1 : NULL; - - /* - * Ok, sanity check. - * If first sample is not a DC_SAMPLE_TIME, Allocate a sample for us - */ - if (sample == NULL && type != DC_SAMPLE_TIME) - sample = prepare_sample(dc); - - switch (type) { - case DC_SAMPLE_TIME: - nsensor = 0; - - // The previous sample gets some sticky values - // that may have been around from before, even - // if there was no new data - if (sample) { - sample->in_deco = in_deco; - sample->ndl.seconds = ndl; - sample->stoptime.seconds = stoptime; - sample->stopdepth.mm = stopdepth; - sample->setpoint.mbar = po2; - sample->cns = cns; - } - // Create a new sample. - // Mark depth as negative - sample = prepare_sample(dc); - sample->time.seconds = value.time; - sample->depth.mm = -1; - finish_sample(dc); - break; - case DC_SAMPLE_DEPTH: - sample->depth.mm = rint(value.depth * 1000); - break; - case DC_SAMPLE_PRESSURE: - sample->sensor = value.pressure.tank; - sample->cylinderpressure.mbar = rint(value.pressure.value * 1000); - break; - case DC_SAMPLE_TEMPERATURE: - sample->temperature.mkelvin = C_to_mkelvin(value.temperature); - break; - case DC_SAMPLE_EVENT: - handle_event(dc, sample, value); - break; - case DC_SAMPLE_RBT: - sample->rbt.seconds = (!strncasecmp(dc->model, "suunto", 6)) ? value.rbt : value.rbt * 60; - break; - case DC_SAMPLE_HEARTBEAT: - sample->heartbeat = value.heartbeat; - break; - case DC_SAMPLE_BEARING: - sample->bearing.degrees = value.bearing; - break; -#ifdef DEBUG_DC_VENDOR - case DC_SAMPLE_VENDOR: - printf(" <vendor time='%u:%02u' type=\"%u\" size=\"%u\">", FRACTION(sample->time.seconds, 60), - value.vendor.type, value.vendor.size); - for (int i = 0; i < value.vendor.size; ++i) - printf("%02X", ((unsigned char *)value.vendor.data)[i]); - printf("</vendor>\n"); - break; -#endif -#if DC_VERSION_CHECK(0, 3, 0) - case DC_SAMPLE_SETPOINT: - /* for us a setpoint means constant pO2 from here */ - sample->setpoint.mbar = po2 = rint(value.setpoint * 1000); - break; - case DC_SAMPLE_PPO2: - if (nsensor < 3) - sample->o2sensor[nsensor].mbar = rint(value.ppo2 * 1000); - else - report_error("%d is more o2 sensors than we can handle", nsensor); - nsensor++; - // Set the amount of detected o2 sensors - if (nsensor > dc->no_o2sensors) - dc->no_o2sensors = nsensor; - break; - case DC_SAMPLE_CNS: - sample->cns = cns = rint(value.cns * 100); - break; - case DC_SAMPLE_DECO: - if (value.deco.type == DC_DECO_NDL) { - sample->ndl.seconds = ndl = value.deco.time; - sample->stopdepth.mm = stopdepth = rint(value.deco.depth * 1000.0); - sample->in_deco = in_deco = false; - } else if (value.deco.type == DC_DECO_DECOSTOP || - value.deco.type == DC_DECO_DEEPSTOP) { - sample->in_deco = in_deco = true; - sample->stopdepth.mm = stopdepth = rint(value.deco.depth * 1000.0); - sample->stoptime.seconds = stoptime = value.deco.time; - ndl = 0; - } else if (value.deco.type == DC_DECO_SAFETYSTOP) { - sample->in_deco = in_deco = false; - sample->stopdepth.mm = stopdepth = rint(value.deco.depth * 1000.0); - sample->stoptime.seconds = stoptime = value.deco.time; - } -#endif - default: - break; - } -} - -static void dev_info(device_data_t *devdata, const char *fmt, ...) -{ - (void) devdata; - static char buffer[1024]; - va_list ap; - - va_start(ap, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, ap); - va_end(ap); - progress_bar_text = buffer; -} - -static int import_dive_number = 0; - -static int parse_samples(device_data_t *devdata, struct divecomputer *dc, dc_parser_t *parser) -{ - (void) devdata; - // Parse the sample data. - return dc_parser_samples_foreach(parser, sample_cb, dc); -} - -static int might_be_same_dc(struct divecomputer *a, struct divecomputer *b) -{ - if (!a->model || !b->model) - return 1; - if (strcasecmp(a->model, b->model)) - return 0; - if (!a->deviceid || !b->deviceid) - return 1; - return a->deviceid == b->deviceid; -} - -static int match_one_dive(struct divecomputer *a, struct dive *dive) -{ - struct divecomputer *b = &dive->dc; - - /* - * Walk the existing dive computer data, - * see if we have a match (or an anti-match: - * the same dive computer but a different - * dive ID). - */ - do { - int match = match_one_dc(a, b); - if (match) - return match > 0; - b = b->next; - } while (b); - - /* Ok, no exact dive computer match. Does the date match? */ - b = &dive->dc; - do { - if (a->when == b->when && might_be_same_dc(a, b)) - return 1; - b = b->next; - } while (b); - - return 0; -} - -/* - * Check if this dive already existed before the import - */ -static int find_dive(struct divecomputer *match) -{ - int i; - - for (i = 0; i < dive_table.preexisting; i++) { - struct dive *old = dive_table.dives[i]; - - if (match_one_dive(match, old)) - return 1; - } - return 0; -} - -/* - * Like g_strdup_printf(), but without the stupid g_malloc/g_free confusion. - * And we limit the string to some arbitrary size. - */ -static char *str_printf(const char *fmt, ...) -{ - va_list args; - char buf[1024]; - - va_start(args, fmt); - vsnprintf(buf, sizeof(buf) - 1, fmt, args); - va_end(args); - buf[sizeof(buf) - 1] = 0; - return strdup(buf); -} - -/* - * The dive ID for libdivecomputer dives is the first word of the - * SHA1 of the fingerprint, if it exists. - * - * NOTE! This is byte-order dependent, and I don't care. - */ -static uint32_t calculate_diveid(const unsigned char *fingerprint, unsigned int fsize) -{ - uint32_t csum[5]; - - if (!fingerprint || !fsize) - return 0; - - SHA1(fingerprint, fsize, (unsigned char *)csum); - return csum[0]; -} - -#ifdef DC_FIELD_STRING -static uint32_t calculate_string_hash(const char *str) -{ - return calculate_diveid((const unsigned char *)str, strlen(str)); -} - -static void parse_string_field(struct dive *dive, dc_field_string_t *str) -{ - // Our dive ID is the string hash of the "Dive ID" string - if (!strcmp(str->desc, "Dive ID")) { - if (!dive->dc.diveid) - dive->dc.diveid = calculate_string_hash(str->value); - return; - } - add_extra_data(&dive->dc, str->desc, str->value); - if (!strcmp(str->desc, "Serial")) { - dive->dc.serial = strdup(str->value); - /* should we just overwrite this whenever we have the "Serial" field? - * It's a much better deviceid then what we have so far... for now I'm leaving it as is */ - if (!dive->dc.deviceid) - dive->dc.deviceid = calculate_string_hash(str->value); - return; - } - if (!strcmp(str->desc, "FW Version")) { - dive->dc.fw_version = strdup(str->value); - return; - } -} -#endif - -static dc_status_t libdc_header_parser(dc_parser_t *parser, struct device_data_t *devdata, struct dive *dive) -{ - dc_status_t rc = 0; - dc_datetime_t dt = { 0 }; - struct tm tm; - - rc = dc_parser_get_datetime(parser, &dt); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing the datetime")); - return rc; - } - - dive->dc.deviceid = devdata->deviceid; - - if (rc == DC_STATUS_SUCCESS) { - tm.tm_year = dt.year; - tm.tm_mon = dt.month - 1; - tm.tm_mday = dt.day; - tm.tm_hour = dt.hour; - tm.tm_min = dt.minute; - tm.tm_sec = dt.second; - dive->when = dive->dc.when = utc_mktime(&tm); - } - - // Parse the divetime. - const char *date_string = get_dive_date_c_string(dive->when); - dev_info(devdata, translate("gettextFromC", "Dive %d: %s"), import_dive_number, date_string); - free((void *)date_string); - - unsigned int divetime = 0; - rc = dc_parser_get_field(parser, DC_FIELD_DIVETIME, 0, &divetime); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing the divetime")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - dive->dc.duration.seconds = divetime; - - // Parse the maxdepth. - double maxdepth = 0.0; - rc = dc_parser_get_field(parser, DC_FIELD_MAXDEPTH, 0, &maxdepth); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing the maxdepth")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - dive->dc.maxdepth.mm = rint(maxdepth * 1000); - -#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) - // if this is defined then we have a fairly late version of libdivecomputer - // from the 0.5 development cylcle - most likely temperatures and tank sizes - // are supported - - // Parse temperatures - double temperature; - dc_field_type_t temp_fields[] = {DC_FIELD_TEMPERATURE_SURFACE, - DC_FIELD_TEMPERATURE_MAXIMUM, - DC_FIELD_TEMPERATURE_MINIMUM}; - for (int i = 0; i < 3; i++) { - rc = dc_parser_get_field(parser, temp_fields[i], 0, &temperature); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing temperature")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - switch(i) { - case 0: - dive->dc.airtemp.mkelvin = C_to_mkelvin(temperature); - break; - case 1: // we don't distinguish min and max water temp here, so take min if given, max otherwise - case 2: - dive->dc.watertemp.mkelvin = C_to_mkelvin(temperature); - break; - } - } -#endif - - // Parse the gas mixes. - unsigned int ngases = 0; - rc = dc_parser_get_field(parser, DC_FIELD_GASMIX_COUNT, 0, &ngases); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing the gas mix count")); - return rc; - } - -#if DC_VERSION_CHECK(0, 3, 0) - // Check if the libdivecomputer version already supports salinity & atmospheric - dc_salinity_t salinity = { - .type = DC_WATER_SALT, - .density = SEAWATER_SALINITY / 10.0 - }; - rc = dc_parser_get_field(parser, DC_FIELD_SALINITY, 0, &salinity); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error obtaining water salinity")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - dive->dc.salinity = rint(salinity.density * 10.0); - - double surface_pressure = 0; - rc = dc_parser_get_field(parser, DC_FIELD_ATMOSPHERIC, 0, &surface_pressure); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error obtaining surface pressure")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - dive->dc.surface_pressure.mbar = rint(surface_pressure * 1000.0); -#endif - -#ifdef DC_FIELD_STRING - // The dive parsing may give us more device information - int idx; - for (idx = 0; idx < 100; idx++) { - dc_field_string_t str = { NULL }; - rc = dc_parser_get_field(parser, DC_FIELD_STRING, idx, &str); - if (rc != DC_STATUS_SUCCESS) - break; - if (!str.desc || !str.value) - break; - parse_string_field(dive, &str); - } -#endif - -#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) - dc_divemode_t divemode; - rc = dc_parser_get_field(parser, DC_FIELD_DIVEMODE, 0, &divemode); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error obtaining divemode")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - switch(divemode) { - case DC_DIVEMODE_FREEDIVE: - dive->dc.divemode = FREEDIVE; - break; - case DC_DIVEMODE_GAUGE: - case DC_DIVEMODE_OC: /* Open circuit */ - dive->dc.divemode = OC; - break; - case DC_DIVEMODE_CC: /* Closed circuit */ - dive->dc.divemode = CCR; - break; - } -#endif - - rc = parse_gasmixes(devdata, dive, parser, ngases); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing the gas mix")); - return rc; - } - - return DC_STATUS_SUCCESS; -} - -/* returns true if we want libdivecomputer's dc_device_foreach() to continue, - * false otherwise */ -static int dive_cb(const unsigned char *data, unsigned int size, - const unsigned char *fingerprint, unsigned int fsize, - void *userdata) -{ - int rc; - dc_parser_t *parser = NULL; - device_data_t *devdata = userdata; - struct dive *dive = NULL; - - /* reset the deco / ndl data */ - ndl = stoptime = stopdepth = 0; - in_deco = false; - - rc = create_parser(devdata, &parser); - if (rc != DC_STATUS_SUCCESS) { - dev_info(devdata, translate("gettextFromC", "Unable to create parser for %s %s"), devdata->vendor, devdata->product); - return false; - } - - rc = dc_parser_set_data(parser, data, size); - if (rc != DC_STATUS_SUCCESS) { - dev_info(devdata, translate("gettextFromC", "Error registering the data")); - goto error_exit; - } - - import_dive_number++; - dive = alloc_dive(); - - // Parse the dive's header data - rc = libdc_header_parser (parser, devdata, dive); - if (rc != DC_STATUS_SUCCESS) { - dev_info(devdata, translate("getextFromC", "Error parsing the header")); - goto error_exit; - } - - dive->dc.model = strdup(devdata->model); - dive->dc.diveid = calculate_diveid(fingerprint, fsize); - - // Initialize the sample data. - rc = parse_samples(devdata, &dive->dc, parser); - if (rc != DC_STATUS_SUCCESS) { - dev_info(devdata, translate("gettextFromC", "Error parsing the samples")); - goto error_exit; - } - - /* If we already saw this dive, abort. */ - if (!devdata->force_download && find_dive(&dive->dc)) - goto error_exit; - - dc_parser_destroy(parser); - - /* Various libdivecomputer interface fixups */ - if (dive->dc.airtemp.mkelvin == 0 && first_temp_is_air && dive->dc.samples) { - dive->dc.airtemp = dive->dc.sample[0].temperature; - dive->dc.sample[0].temperature.mkelvin = 0; - } - - if (devdata->create_new_trip) { - if (!devdata->trip) - devdata->trip = create_and_hookup_trip_from_dive(dive); - else - add_dive_to_trip(dive, devdata->trip); - } - - dive->downloaded = true; - record_dive_to_table(dive, devdata->download_table); - mark_divelist_changed(true); - return true; - -error_exit: - dc_parser_destroy(parser); - free(dive); - return false; - -} - -/* - * The device ID for libdivecomputer devices is the first 32-bit word - * of the SHA1 hash of the model/firmware/serial numbers. - * - * NOTE! This is byte-order-dependent. And I can't find it in myself to - * care. - */ -static uint32_t calculate_sha1(unsigned int model, unsigned int firmware, unsigned int serial) -{ - SHA_CTX ctx; - uint32_t csum[5]; - - SHA1_Init(&ctx); - SHA1_Update(&ctx, &model, sizeof(model)); - SHA1_Update(&ctx, &firmware, sizeof(firmware)); - SHA1_Update(&ctx, &serial, sizeof(serial)); - SHA1_Final((unsigned char *)csum, &ctx); - return csum[0]; -} - -/* - * libdivecomputer has returned two different serial numbers for the - * same device in different versions. First it used to just do the four - * bytes as one 32-bit number, then it turned it into a decimal number - * with each byte giving two digits (0-99). - * - * The only way we can tell is by looking at the format of the number, - * so we'll just fix it to the first format. - */ -static unsigned int undo_libdivecomputer_suunto_nr_changes(unsigned int serial) -{ - unsigned char b0, b1, b2, b3; - - /* - * The second format will never have more than 8 decimal - * digits, so do a cheap check first - */ - if (serial >= 100000000) - return serial; - - /* The original format seems to be four bytes of values 00-99 */ - b0 = (serial >> 0) & 0xff; - b1 = (serial >> 8) & 0xff; - b2 = (serial >> 16) & 0xff; - b3 = (serial >> 24) & 0xff; - - /* Looks like an old-style libdivecomputer serial number */ - if ((b0 < 100) && (b1 < 100) && (b2 < 100) && (b3 < 100)) - return serial; - - /* Nope, it was converted. */ - b0 = serial % 100; - serial /= 100; - b1 = serial % 100; - serial /= 100; - b2 = serial % 100; - serial /= 100; - b3 = serial % 100; - - serial = b0 + (b1 << 8) + (b2 << 16) + (b3 << 24); - return serial; -} - -static unsigned int fixup_suunto_versions(device_data_t *devdata, const dc_event_devinfo_t *devinfo) -{ - unsigned int serial = devinfo->serial; - char serial_nr[13] = ""; - char firmware[13] = ""; - - first_temp_is_air = 1; - - serial = undo_libdivecomputer_suunto_nr_changes(serial); - - if (serial) { - snprintf(serial_nr, sizeof(serial_nr), "%02d%02d%02d%02d", - (devinfo->serial >> 24) & 0xff, - (devinfo->serial >> 16) & 0xff, - (devinfo->serial >> 8) & 0xff, - (devinfo->serial >> 0) & 0xff); - } - if (devinfo->firmware) { - snprintf(firmware, sizeof(firmware), "%d.%d.%d", - (devinfo->firmware >> 16) & 0xff, - (devinfo->firmware >> 8) & 0xff, - (devinfo->firmware >> 0) & 0xff); - } - create_device_node(devdata->model, devdata->deviceid, serial_nr, firmware, ""); - - return serial; -} - -static void event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata) -{ - (void) device; - const dc_event_progress_t *progress = data; - const dc_event_devinfo_t *devinfo = data; - const dc_event_clock_t *clock = data; - const dc_event_vendor_t *vendor = data; - device_data_t *devdata = userdata; - unsigned int serial; - - switch (event) { - case DC_EVENT_WAITING: - dev_info(devdata, translate("gettextFromC", "Event: waiting for user action")); - break; - case DC_EVENT_PROGRESS: - if (!progress->maximum) - break; - progress_bar_fraction = (double)progress->current / (double)progress->maximum; - break; - case DC_EVENT_DEVINFO: - dev_info(devdata, translate("gettextFromC", "model=%u (0x%08x), firmware=%u (0x%08x), serial=%u (0x%08x)"), - devinfo->model, devinfo->model, - devinfo->firmware, devinfo->firmware, - devinfo->serial, devinfo->serial); - if (devdata->libdc_logfile) { - fprintf(devdata->libdc_logfile, "Event: model=%u (0x%08x), firmware=%u (0x%08x), serial=%u (0x%08x)\n", - devinfo->model, devinfo->model, - devinfo->firmware, devinfo->firmware, - devinfo->serial, devinfo->serial); - } - /* - * libdivecomputer doesn't give serial numbers in the proper string form, - * so we have to see if we can do some vendor-specific munging. - */ - serial = devinfo->serial; - if (!strcmp(devdata->vendor, "Suunto")) - serial = fixup_suunto_versions(devdata, devinfo); - devdata->deviceid = calculate_sha1(devinfo->model, devinfo->firmware, serial); - /* really, serial and firmware version are NOT numbers. We'll try to save them here - * in something that might work, but this really needs to be handled with the - * DC_FIELD_STRING interface instead */ - devdata->libdc_serial = devinfo->serial; - devdata->libdc_firmware = devinfo->firmware; - break; - case DC_EVENT_CLOCK: - dev_info(devdata, translate("gettextFromC", "Event: systime=%" PRId64 ", devtime=%u\n"), - (uint64_t)clock->systime, clock->devtime); - if (devdata->libdc_logfile) { - fprintf(devdata->libdc_logfile, "Event: systime=%" PRId64 ", devtime=%u\n", - (uint64_t)clock->systime, clock->devtime); - } - break; - case DC_EVENT_VENDOR: - if (devdata->libdc_logfile) { - fprintf(devdata->libdc_logfile, "Event: vendor="); - for (unsigned int i = 0; i < vendor->size; ++i) - fprintf(devdata->libdc_logfile, "%02X", vendor->data[i]); - fprintf(devdata->libdc_logfile, "\n"); - } - break; - default: - break; - } -} - -int import_thread_cancelled; - -static int cancel_cb(void *userdata) -{ - (void) userdata; - return import_thread_cancelled; -} - -static const char *do_device_import(device_data_t *data) -{ - dc_status_t rc; - dc_device_t *device = data->device; - - data->model = str_printf("%s %s", data->vendor, data->product); - - // Register the event handler. - int events = DC_EVENT_WAITING | DC_EVENT_PROGRESS | DC_EVENT_DEVINFO | DC_EVENT_CLOCK | DC_EVENT_VENDOR; - rc = dc_device_set_events(device, events, event_cb, data); - if (rc != DC_STATUS_SUCCESS) - return translate("gettextFromC", "Error registering the event handler."); - - // Register the cancellation handler. - rc = dc_device_set_cancel(device, cancel_cb, data); - if (rc != DC_STATUS_SUCCESS) - return translate("gettextFromC", "Error registering the cancellation handler."); - - if (data->libdc_dump) { - dc_buffer_t *buffer = dc_buffer_new(0); - - rc = dc_device_dump(device, buffer); - if (rc == DC_STATUS_SUCCESS && dumpfile_name) { - FILE *fp = subsurface_fopen(dumpfile_name, "wb"); - if (fp != NULL) { - fwrite(dc_buffer_get_data(buffer), 1, dc_buffer_get_size(buffer), fp); - fclose(fp); - } - } - - dc_buffer_free(buffer); - } else { - rc = dc_device_foreach(device, dive_cb, data); - } - - if (rc != DC_STATUS_SUCCESS) { - progress_bar_fraction = 0.0; - return translate("gettextFromC", "Dive data import error"); - } - - /* All good */ - return NULL; -} - -void logfunc(dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *msg, void *userdata) -{ - (void) context; - const char *loglevels[] = { "NONE", "ERROR", "WARNING", "INFO", "DEBUG", "ALL" }; - - FILE *fp = (FILE *)userdata; - - if (loglevel == DC_LOGLEVEL_ERROR || loglevel == DC_LOGLEVEL_WARNING) { - fprintf(fp, "%s: %s [in %s:%d (%s)]\n", loglevels[loglevel], msg, file, line, function); - } else { - fprintf(fp, "%s: %s\n", loglevels[loglevel], msg); - } -} - -const char *do_libdivecomputer_import(device_data_t *data) -{ - dc_status_t rc; - const char *err; - FILE *fp = NULL; - - import_dive_number = 0; - first_temp_is_air = 0; - data->device = NULL; - data->context = NULL; - - if (data->libdc_log && logfile_name) - fp = subsurface_fopen(logfile_name, "w"); - - data->libdc_logfile = fp; - - rc = dc_context_new(&data->context); - if (rc != DC_STATUS_SUCCESS) - return translate("gettextFromC", "Unable to create libdivecomputer context"); - - if (fp) { - dc_context_set_loglevel(data->context, DC_LOGLEVEL_ALL); - dc_context_set_logfunc(data->context, logfunc, fp); - } - - err = translate("gettextFromC", "Unable to open %s %s (%s)"); - -#if defined(SSRF_CUSTOM_SERIAL) - dc_serial_t *serial_device = NULL; - - if (data->bluetooth_mode) { -#if defined(BT_SUPPORT) && defined(SSRF_CUSTOM_SERIAL) - rc = dc_serial_qt_open(&serial_device, data->context, data->devname); -#endif -#ifdef SERIAL_FTDI - } else if (!strcmp(data->devname, "ftdi")) { - rc = dc_serial_ftdi_open(&serial_device, data->context); -#endif - } - - if (rc != DC_STATUS_SUCCESS) { - report_error(errmsg(rc)); - } else if (serial_device) { - rc = dc_device_custom_open(&data->device, data->context, data->descriptor, serial_device); - } else { -#else - { -#endif - rc = dc_device_open(&data->device, data->context, data->descriptor, data->devname); - - if (rc != DC_STATUS_SUCCESS && subsurface_access(data->devname, R_OK | W_OK) != 0) - err = translate("gettextFromC", "Insufficient privileges to open the device %s %s (%s)"); - } - - if (rc == DC_STATUS_SUCCESS) { - err = do_device_import(data); - /* TODO: Show the logfile to the user on error. */ - dc_device_close(data->device); - data->device = NULL; - } - - dc_context_free(data->context); - data->context = NULL; - - if (fp) { - fclose(fp); - } - - return err; -} - -/* - * Parse data buffers instead of dc devices downloaded data. - * Intended to be used to parse profile data from binary files during import tasks. - * Actually included Uwatec families because of works on datatrak and smartrak logs - * and OSTC families for OSTCTools logs import. - * For others, simply include them in the switch (check parameters). - * Note that dc_descriptor_t in data *must* have been filled using dc_descriptor_iterator() - * calls. - */ -dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned char *buffer, int size) -{ - dc_status_t rc; - dc_parser_t *parser = NULL; - - switch (data->descriptor->type) { - case DC_FAMILY_UWATEC_ALADIN: - case DC_FAMILY_UWATEC_MEMOMOUSE: - rc = uwatec_memomouse_parser_create(&parser, data->context, 0, 0); - break; - case DC_FAMILY_UWATEC_SMART: - case DC_FAMILY_UWATEC_MERIDIAN: - rc = uwatec_smart_parser_create (&parser, data->context, data->descriptor->model, 0, 0); - break; - case DC_FAMILY_HW_OSTC: -#if defined(SSRF_CUSTOM_SERIAL) - rc = hw_ostc_parser_create (&parser, data->context, data->deviceid, 0); -#else - rc = hw_ostc_parser_create (&parser, data->context, data->deviceid); -#endif - break; - case DC_FAMILY_HW_FROG: - case DC_FAMILY_HW_OSTC3: -#if defined(SSRF_CUSTOM_SERIAL) - rc = hw_ostc_parser_create (&parser, data->context, data->deviceid, 1); -#else - rc = hw_ostc_parser_create (&parser, data->context, data->deviceid); -#endif - break; - default: - report_error("Device type not handled!"); - return DC_STATUS_UNSUPPORTED; - } - if (rc != DC_STATUS_SUCCESS) { - report_error("Error creating parser."); - dc_parser_destroy (parser); - return rc; - } - rc = dc_parser_set_data(parser, buffer, size); - if (rc != DC_STATUS_SUCCESS) { - report_error("Error registering the data."); - dc_parser_destroy (parser); - return rc; - } - // Do not parse Aladin/Memomouse headers as they are fakes - // Do not return on error, we can still parse the samples - if (data->descriptor->type != DC_FAMILY_UWATEC_ALADIN && data->descriptor->type != DC_FAMILY_UWATEC_MEMOMOUSE) { - rc = libdc_header_parser (parser, data, dive); - if (rc != DC_STATUS_SUCCESS) { - report_error("Error parsing the dive header data. Dive # %d\nStatus = %s", dive->number, errmsg(rc)); - } - } - rc = dc_parser_samples_foreach (parser, sample_cb, &dive->dc); - if (rc != DC_STATUS_SUCCESS) { - report_error("Error parsing the sample data. Dive # %d\nStatus = %s", dive->number, errmsg(rc)); - dc_parser_destroy (parser); - return rc; - } - dc_parser_destroy(parser); - return(DC_STATUS_SUCCESS); -} diff --git a/subsurface-core/libdivecomputer.h b/subsurface-core/libdivecomputer.h deleted file mode 100644 index 99f1c2490..000000000 --- a/subsurface-core/libdivecomputer.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef LIBDIVECOMPUTER_H -#define LIBDIVECOMPUTER_H - - -/* libdivecomputer */ - -#ifdef DC_VERSION /* prevent a warning with wingdi.h */ -#undef DC_VERSION -#endif -#ifdef HAVE_LIBDIVECOMPUTER -#include <libdivecomputer/version.h> -#endif -#include <libdivecomputer/device.h> -#include <libdivecomputer/parser.h> - -#include "dive.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct dc_descriptor_t { - const char *vendor; - const char *product; - dc_family_t type; - unsigned int model; -}; - -/* don't forget to include the UI toolkit specific display-XXX.h first - to get the definition of progressbar_t */ -typedef struct device_data_t -{ - dc_descriptor_t *descriptor; - const char *vendor, *product, *devname; - const char *model; - uint32_t libdc_firmware, libdc_serial; - uint32_t deviceid, diveid; - dc_device_t *device; - dc_context_t *context; - struct dive_trip *trip; - int preexisting; - bool force_download; - bool create_new_trip; - bool libdc_log; - bool libdc_dump; - bool bluetooth_mode; - FILE *libdc_logfile; - struct dive_table *download_table; -} device_data_t; - -const char *errmsg (dc_status_t rc); -const char *do_libdivecomputer_import(device_data_t *data); -const char *do_uemis_import(device_data_t *data); -dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned char *buffer, int size); -void logfunc(dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *msg, void *userdata); - -extern int import_thread_cancelled; -extern const char *progress_bar_text; -extern double progress_bar_fraction; -extern char *logfile_name; -extern char *dumpfile_name; - -#if SSRF_CUSTOM_SERIAL -extern dc_status_t dc_serial_qt_open(dc_serial_t **out, dc_context_t *context, const char *devaddr); -extern dc_status_t dc_serial_ftdi_open(dc_serial_t **out, dc_context_t *context); -#endif - -#ifdef __cplusplus -} -#endif - -#endif // LIBDIVECOMPUTER_H diff --git a/subsurface-core/linux.c b/subsurface-core/linux.c deleted file mode 100644 index b81f6bf53..000000000 --- a/subsurface-core/linux.c +++ /dev/null @@ -1,232 +0,0 @@ -/* linux.c */ -/* implements Linux specific functions */ -#include "dive.h" -#include "display.h" -#include "membuffer.h" -#include <string.h> -#include <sys/types.h> -#include <dirent.h> -#include <fnmatch.h> -#include <stdio.h> -#include <fcntl.h> -#include <unistd.h> -#include <pwd.h> - -// the DE should provide us with a default font and font size... -const char linux_system_divelist_default_font[] = "Sans"; -const char *system_divelist_default_font = linux_system_divelist_default_font; -double system_divelist_default_font_size = -1.0; - -void subsurface_OS_pref_setup(void) -{ - // nothing -} - -bool subsurface_ignore_font(const char *font) -{ - // there are no old default fonts to ignore - (void)font; - return false; -} - -void subsurface_user_info(struct user_info *user) -{ - struct passwd *pwd = getpwuid(getuid()); - const char *username = getenv("USER"); - - if (pwd) { - if (pwd->pw_gecos && *pwd->pw_gecos) - user->name = pwd->pw_gecos; - if (!username) - username = pwd->pw_name; - } - if (username && *username) { - char hostname[64]; - struct membuffer mb = {}; - gethostname(hostname, sizeof(hostname)); - put_format(&mb, "%s@%s", username, hostname); - user->email = mb_cstring(&mb); - } -} - -static const char *system_default_path_append(const char *append) -{ - const char *home = getenv("HOME"); - if (!home) - home = "~"; - const char *path = "/.subsurface"; - - int len = strlen(home) + strlen(path) + 1; - if (append) - len += strlen(append) + 1; - - char *buffer = (char *)malloc(len); - memset(buffer, 0, len); - strcat(buffer, home); - strcat(buffer, path); - if (append) { - strcat(buffer, "/"); - strcat(buffer, append); - } - - return buffer; -} - -const char *system_default_directory(void) -{ - static const char *path = NULL; - if (!path) - path = system_default_path_append(NULL); - return path; -} - -const char *system_default_filename(void) -{ - static char *filename = NULL; - if (!filename) { - const char *user = getenv("LOGNAME"); - if (same_string(user, "")) - user = "username"; - filename = calloc(strlen(user) + 5, 1); - strcat(filename, user); - strcat(filename, ".xml"); - } - static const char *path = NULL; - if (!path) - path = system_default_path_append(filename); - return path; -} - -int enumerate_devices(device_callback_t callback, void *userdata, int dc_type) -{ - int index = -1, entries = 0; - DIR *dp = NULL; - struct dirent *ep = NULL; - size_t i; - FILE *file; - char *line = NULL; - char *fname; - size_t len; - if (dc_type != DC_TYPE_UEMIS) { - const char *dirname = "/dev"; - const char *patterns[] = { - "ttyUSB*", - "ttyS*", - "ttyACM*", - "rfcomm*", - NULL - }; - - dp = opendir(dirname); - if (dp == NULL) { - return -1; - } - - while ((ep = readdir(dp)) != NULL) { - for (i = 0; patterns[i] != NULL; ++i) { - if (fnmatch(patterns[i], ep->d_name, 0) == 0) { - char filename[1024]; - int n = snprintf(filename, sizeof(filename), "%s/%s", dirname, ep->d_name); - if (n >= (int)sizeof(filename)) { - closedir(dp); - return -1; - } - callback(filename, userdata); - if (is_default_dive_computer_device(filename)) - index = entries; - entries++; - break; - } - } - } - closedir(dp); - } - if (dc_type != DC_TYPE_SERIAL) { - int num_uemis = 0; - file = fopen("/proc/mounts", "r"); - if (file == NULL) - return index; - - while ((getline(&line, &len, file)) != -1) { - char *ptr = strstr(line, "UEMISSDA"); - if (ptr) { - char *end = ptr, *start = ptr; - while (start > line && *start != ' ') - start--; - if (*start == ' ') - start++; - while (*end != ' ' && *end != '\0') - end++; - - *end = '\0'; - fname = strdup(start); - - callback(fname, userdata); - - if (is_default_dive_computer_device(fname)) - index = entries; - entries++; - num_uemis++; - free((void *)fname); - } - } - free(line); - fclose(file); - if (num_uemis == 1 && entries == 1) /* if we found only one and it's a mounted Uemis, pick it */ - index = 0; - } - return index; -} - -/* NOP wrappers to comform with windows.c */ -int subsurface_rename(const char *path, const char *newpath) -{ - return rename(path, newpath); -} - -int subsurface_open(const char *path, int oflags, mode_t mode) -{ - return open(path, oflags, mode); -} - -FILE *subsurface_fopen(const char *path, const char *mode) -{ - return fopen(path, mode); -} - -void *subsurface_opendir(const char *path) -{ - return (void *)opendir(path); -} - -int subsurface_access(const char *path, int mode) -{ - return access(path, mode); -} - -struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp) -{ - return zip_open(path, flags, errorp); -} - -int subsurface_zip_close(struct zip *zip) -{ - return zip_close(zip); -} - -/* win32 console */ -void subsurface_console_init(bool dedicated) -{ - (void)dedicated; - /* NOP */ -} - -void subsurface_console_exit(void) -{ - /* NOP */ -} - -bool subsurface_user_is_root() -{ - return (geteuid() == 0); -} diff --git a/subsurface-core/liquivision.c b/subsurface-core/liquivision.c deleted file mode 100644 index 9347a724a..000000000 --- a/subsurface-core/liquivision.c +++ /dev/null @@ -1,420 +0,0 @@ -#include <string.h> - -#include "dive.h" -#include "divelist.h" -#include "file.h" -#include "strndup.h" - -// Convert bytes into an INT -#define array_uint16_le(p) ((unsigned int) (p)[0] \ - + ((p)[1]<<8) ) -#define array_uint32_le(p) ((unsigned int) (p)[0] \ - + ((p)[1]<<8) + ((p)[2]<<16) \ - + ((p)[3]<<24)) - -struct lv_event { - time_t time; - struct pressure { - int sensor; - int mbar; - } pressure; -}; - -uint16_t primary_sensor; - -static int handle_event_ver2(int code, const unsigned char *ps, unsigned int ps_ptr, struct lv_event *event) -{ - (void) code; - (void) ps; - (void) ps_ptr; - (void) event; - - // Skip 4 bytes - return 4; -} - - -static int handle_event_ver3(int code, const unsigned char *ps, unsigned int ps_ptr, struct lv_event *event) -{ - int skip = 4; - uint16_t current_sensor; - - switch (code) { - case 0x0002: // Unknown - case 0x0004: // Unknown - skip = 4; - break; - case 0x0005: // Unknown - skip = 6; - break; - case 0x0007: // Gas - // 4 byte time - // 1 byte O2, 1 bye He - skip = 6; - break; - case 0x0008: - // 4 byte time - // 2 byte gas set point 2 - skip = 6; - break; - case 0x000f: - // Tank pressure - event->time = array_uint32_le(ps + ps_ptr); - - /* As far as I know, Liquivision supports 2 sensors, own and buddie's. This is my - * best guess how it is represented. */ - - current_sensor = array_uint16_le(ps + ps_ptr + 4); - if (primary_sensor == 0) { - primary_sensor = current_sensor; - } - if (current_sensor == primary_sensor) { - event->pressure.sensor = 0; - event->pressure.mbar = array_uint16_le(ps + ps_ptr + 6) * 10; // cb->mb - } else { - /* Ignoring the buddy sensor for no as we cannot draw it on the profile. - event->pressure.sensor = 1; - event->pressure.mbar = array_uint16_le(ps + ps_ptr + 6) * 10; // cb->mb - */ - } - // 1 byte PSR - // 1 byte ST - skip = 10; - break; - case 0x0010: - skip = 26; - break; - case 0x0015: // Unknown - skip = 2; - break; - default: - skip = 4; - break; - } - - return skip; -} - -static void parse_dives (int log_version, const unsigned char *buf, unsigned int buf_size) -{ - unsigned int ptr = 0; - unsigned char model; - - struct dive *dive; - struct divecomputer *dc; - struct sample *sample; - - while (ptr < buf_size) { - int i; - bool found_divesite = false; - dive = alloc_dive(); - primary_sensor = 0; - dc = &dive->dc; - - /* Just the main cylinder until we can handle the buddy cylinder porperly */ - for (i = 0; i < 1; i++) - fill_default_cylinder(&dive->cylinder[i]); - - // Model 0=Xen, 1,2=Xeo, 4=Lynx, other=Liquivision - model = *(buf + ptr); - switch (model) { - case 0: - dc->model = strdup("Xen"); - break; - case 1: - case 2: - dc->model = strdup("Xeo"); - break; - case 4: - dc->model = strdup("Lynx"); - break; - default: - dc->model = strdup("Liquivision"); - break; - } - ptr++; - - // Dive location, assemble Location and Place - unsigned int len, place_len; - char *location; - len = array_uint32_le(buf + ptr); - ptr += 4; - place_len = array_uint32_le(buf + ptr + len); - - if (len && place_len) { - location = malloc(len + place_len + 4); - memset(location, 0, len + place_len + 4); - memcpy(location, buf + ptr, len); - memcpy(location + len, ", ", 2); - memcpy(location + len + 2, buf + ptr + len + 4, place_len); - } else if (len) { - location = strndup((char *)buf + ptr, len); - } else if (place_len) { - location = strndup((char *)buf + ptr + len + 4, place_len); - } - - /* Store the location only if we have one */ - if (len || place_len) - found_divesite = true; - - ptr += len + 4 + place_len; - - // Dive comment - len = array_uint32_le(buf + ptr); - ptr += 4; - - // Blank notes are better than the default text - if (len && strncmp((char *)buf + ptr, "Comment ...", 11)) { - dive->notes = strndup((char *)buf + ptr, len); - } - ptr += len; - - dive->id = array_uint32_le(buf + ptr); - ptr += 4; - - dive->number = array_uint16_le(buf + ptr) + 1; - ptr += 2; - - dive->duration.seconds = array_uint32_le(buf + ptr); // seconds - ptr += 4; - - dive->maxdepth.mm = array_uint16_le(buf + ptr) * 10; // cm->mm - ptr += 2; - - dive->meandepth.mm = array_uint16_le(buf + ptr) * 10; // cm->mm - ptr += 2; - - dive->when = array_uint32_le(buf + ptr); - ptr += 4; - - // now that we have the dive time we can store the divesite - // (we need the dive time to create deterministic uuids) - if (found_divesite) { - dive->dive_site_uuid = find_or_create_dive_site_with_name(location, dive->when); - free(location); - } - //unsigned int end_time = array_uint32_le(buf + ptr); - ptr += 4; - - //unsigned int sit = array_uint32_le(buf + ptr); - ptr += 4; - //if (sit == 0xffffffff) { - //} - - dive->surface_pressure.mbar = array_uint16_le(buf + ptr); // ??? - ptr += 2; - - //unsigned int rep_dive = array_uint16_le(buf + ptr); - ptr += 2; - - dive->mintemp.mkelvin = C_to_mkelvin((float)array_uint16_le(buf + ptr)/10);// C->mK - ptr += 2; - - dive->maxtemp.mkelvin = C_to_mkelvin((float)array_uint16_le(buf + ptr)/10);// C->mK - ptr += 2; - - dive->salinity = *(buf + ptr); // ??? - ptr += 1; - - unsigned int sample_count = array_uint32_le(buf + ptr); - ptr += 4; - - // Sample interval - unsigned char sample_interval; - sample_interval = 1; - - unsigned char intervals[6] = {1,2,5,10,30,60}; - if (*(buf + ptr) < 6) - sample_interval = intervals[*(buf + ptr)]; - ptr += 1; - - float start_cns = 0; - unsigned char dive_mode = 0, algorithm = 0; - if (array_uint32_le(buf + ptr) != sample_count) { - // Xeo, with CNS and OTU - start_cns = *(float *) (buf + ptr); - ptr += 4; - dive->cns = *(float *) (buf + ptr); // end cns - ptr += 4; - dive->otu = *(float *) (buf + ptr); - ptr += 4; - dive_mode = *(buf + ptr++); // 0=Deco, 1=Gauge, 2=None - algorithm = *(buf + ptr++); // 0=ZH-L16C+GF - sample_count = array_uint32_le(buf + ptr); - } - // we aren't using the start_cns, dive_mode, and algorithm, yet - (void)start_cns; - (void)dive_mode; - (void)algorithm; - - ptr += 4; - - // Parse dive samples - const unsigned char *ds = buf + ptr; - const unsigned char *ts = buf + ptr + sample_count * 2 + 4; - const unsigned char *ps = buf + ptr + sample_count * 4 + 4; - unsigned int ps_count = array_uint32_le(ps); - ps += 4; - - // Bump ptr - ptr += sample_count * 4 + 4; - - // Handle events - unsigned int ps_ptr; - ps_ptr = 0; - - unsigned int event_code, d = 0, e; - struct lv_event event; - - // Loop through events - for (e = 0; e < ps_count; e++) { - // Get event - event_code = array_uint16_le(ps + ps_ptr); - ps_ptr += 2; - - if (log_version == 3) { - ps_ptr += handle_event_ver3(event_code, ps, ps_ptr, &event); - if (event_code != 0xf) - continue; // ignore all by pressure sensor event - } else { // version 2 - ps_ptr += handle_event_ver2(event_code, ps, ps_ptr, &event); - continue; // ignore all events - } - int sample_time, last_time; - int depth_mm, last_depth, temp_mk, last_temp; - - while (true) { - sample = prepare_sample(dc); - - // Get sample times - sample_time = d * sample_interval; - depth_mm = array_uint16_le(ds + d * 2) * 10; // cm->mm - temp_mk = C_to_mkelvin((float)array_uint16_le(ts + d * 2) / 10); // dC->mK - last_time = (d ? (d - 1) * sample_interval : 0); - - if (d == sample_count) { - // We still have events to record - sample->time.seconds = event.time; - sample->depth.mm = array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm - sample->temperature.mkelvin = C_to_mkelvin((float) array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK - sample->sensor = event.pressure.sensor; - sample->cylinderpressure.mbar = event.pressure.mbar; - finish_sample(dc); - - break; - } else if (event.time > sample_time) { - // Record sample and loop - sample->time.seconds = sample_time; - sample->depth.mm = depth_mm; - sample->temperature.mkelvin = temp_mk; - finish_sample(dc); - d++; - - continue; - } else if (event.time == sample_time) { - sample->time.seconds = sample_time; - sample->depth.mm = depth_mm; - sample->temperature.mkelvin = temp_mk; - sample->sensor = event.pressure.sensor; - sample->cylinderpressure.mbar = event.pressure.mbar; - finish_sample(dc); - - break; - } else { // Event is prior to sample - sample->time.seconds = event.time; - sample->sensor = event.pressure.sensor; - sample->cylinderpressure.mbar = event.pressure.mbar; - if (last_time == sample_time) { - sample->depth.mm = depth_mm; - sample->temperature.mkelvin = temp_mk; - } else { - // Extrapolate - last_depth = array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm - last_temp = C_to_mkelvin((float) array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK - sample->depth.mm = last_depth + (depth_mm - last_depth) - * (event.time - last_time) / sample_interval; - sample->temperature.mkelvin = last_temp + (temp_mk - last_temp) - * (event.time - last_time) / sample_interval; - } - finish_sample(dc); - - break; - } - } // while (true); - } // for each event sample - - // record trailing depth samples - for ( ;d < sample_count; d++) { - sample = prepare_sample(dc); - sample->time.seconds = d * sample_interval; - - sample->depth.mm = array_uint16_le(ds + d * 2) * 10; // cm->mm - sample->temperature.mkelvin = - C_to_mkelvin((float)array_uint16_le(ts + d * 2) / 10); - finish_sample(dc); - } - - if (log_version == 3 && model == 4) { - // Advance to begin of next dive - switch (array_uint16_le(ps + ps_ptr)) { - case 0x0000: - ps_ptr += 5; - break; - case 0x0100: - ps_ptr += 7; - break; - case 0x0200: - ps_ptr += 9; - break; - case 0x0300: - ps_ptr += 11; - break; - case 0x0b0b: - ps_ptr += 27; - break; - } - - while (*(ps + ps_ptr) != 0x04) - ps_ptr++; - } - - // End dive - dive->downloaded = true; - record_dive(dive); - mark_divelist_changed(true); - - // Advance ptr for next dive - ptr += ps_ptr + 4; - } // while - - //DEBUG save_dives("/tmp/test.xml"); -} - -int try_to_open_liquivision(const char *filename, struct memblock *mem) -{ - (void) filename; - const unsigned char *buf = mem->buffer; - unsigned int buf_size = mem->size; - unsigned int ptr; - int log_version; - - // Get name length - unsigned int len = array_uint32_le(buf); - // Ignore length field and the name - ptr = 4 + len; - - unsigned int dive_count = array_uint32_le(buf + ptr); - if (dive_count == 0xffffffff) { - // File version 3.0 - log_version = 3; - ptr += 6; - dive_count = array_uint32_le(buf + ptr); - } else { - log_version = 2; - } - ptr += 4; - - parse_dives(log_version, buf + ptr, buf_size - ptr); - - return 1; -} diff --git a/subsurface-core/load-git.c b/subsurface-core/load-git.c deleted file mode 100644 index a1f2d2031..000000000 --- a/subsurface-core/load-git.c +++ /dev/null @@ -1,1709 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include <stdio.h> -#include <ctype.h> -#include <string.h> -#include <stdlib.h> -#include <errno.h> -#include <time.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <fcntl.h> -#include <git2.h> -#include <libdivecomputer/parser.h> - -#include "gettext.h" - -#include "dive.h" -#include "divelist.h" -#include "device.h" -#include "membuffer.h" -#include "git-access.h" -#include "qthelperfromc.h" - -const char *saved_git_id = NULL; - -struct picture_entry_list { - void *data; - int len; - const char *hash; - struct picture_entry_list *next; -}; -struct picture_entry_list *pel = NULL; - -struct keyword_action { - const char *keyword; - void (*fn)(char *, struct membuffer *, void *); -}; -#define ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) - -extern degrees_t parse_degrees(char *buf, char **end); -git_blob *git_tree_entry_blob(git_repository *repo, const git_tree_entry *entry); - -static void save_picture_from_git(struct picture *picture) -{ - struct picture_entry_list *pic_entry = pel; - - while (pic_entry) { - if (same_string(pic_entry->hash, picture->hash)) { - savePictureLocal(picture, pic_entry->data, pic_entry->len); - return; - } - pic_entry = pic_entry->next; - } - fprintf(stderr, "didn't find picture entry for %s\n", picture->filename); -} - -static char *get_utf8(struct membuffer *b) -{ - int len = b->len; - char *res; - - if (!len) - return NULL; - res = malloc(len+1); - if (res) { - memcpy(res, b->buffer, len); - res[len] = 0; - } - return res; -} - -static temperature_t get_temperature(const char *line) -{ - temperature_t t; - t.mkelvin = C_to_mkelvin(ascii_strtod(line, NULL)); - return t; -} - -static depth_t get_depth(const char *line) -{ - depth_t d; - d.mm = rint(1000*ascii_strtod(line, NULL)); - return d; -} - -static volume_t get_volume(const char *line) -{ - volume_t v; - v.mliter = rint(1000*ascii_strtod(line, NULL)); - return v; -} - -static weight_t get_weight(const char *line) -{ - weight_t w; - w.grams = rint(1000*ascii_strtod(line, NULL)); - return w; -} - -static pressure_t get_pressure(const char *line) -{ - pressure_t p; - p.mbar = rint(1000*ascii_strtod(line, NULL)); - return p; -} - -static int get_salinity(const char *line) -{ - return rint(10*ascii_strtod(line, NULL)); -} - -static fraction_t get_fraction(const char *line) -{ - fraction_t f; - f.permille = rint(10*ascii_strtod(line, NULL)); - return f; -} - -static void update_date(timestamp_t *when, const char *line) -{ - unsigned yyyy, mm, dd; - struct tm tm; - - if (sscanf(line, "%04u-%02u-%02u", &yyyy, &mm, &dd) != 3) - return; - utc_mkdate(*when, &tm); - tm.tm_year = yyyy - 1900; - tm.tm_mon = mm - 1; - tm.tm_mday = dd; - *when = utc_mktime(&tm); -} - -static void update_time(timestamp_t *when, const char *line) -{ - unsigned h, m, s = 0; - struct tm tm; - - if (sscanf(line, "%02u:%02u:%02u", &h, &m, &s) < 2) - return; - utc_mkdate(*when, &tm); - tm.tm_hour = h; - tm.tm_min = m; - tm.tm_sec = s; - *when = utc_mktime(&tm); -} - -static duration_t get_duration(const char *line) -{ - int m = 0, s = 0; - duration_t d; - sscanf(line, "%d:%d", &m, &s); - d.seconds = m*60+s; - return d; -} - -static enum dive_comp_type get_dctype(const char *line) -{ - for (enum dive_comp_type i = 0; i < NUM_DC_TYPE; i++) { - if (strcmp(line, divemode_text[i]) == 0) - return i; - } - return 0; -} - -static int get_index(const char *line) -{ return atoi(line); } - -static int get_hex(const char *line) -{ return strtoul(line, NULL, 16); } - -/* this is in qthelper.cpp, so including the .h file is a pain */ -extern const char *printGPSCoords(int lat, int lon); - -static void parse_dive_gps(char *line, struct membuffer *str, void *_dive) -{ - (void) str; - uint32_t uuid; - degrees_t latitude = parse_degrees(line, &line); - degrees_t longitude = parse_degrees(line, &line); - struct dive *dive = _dive; - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds) { - uuid = get_dive_site_uuid_by_gps(latitude, longitude, NULL); - if (!uuid) - uuid = create_dive_site_with_gps("", latitude, longitude, dive->when); - dive->dive_site_uuid = uuid; - } else { - if (dive_site_has_gps_location(ds) && - (ds->latitude.udeg != latitude.udeg || ds->longitude.udeg != longitude.udeg)) { - const char *coords = printGPSCoords(latitude.udeg, longitude.udeg); - // we have a dive site that already has GPS coordinates - ds->notes = add_to_string(ds->notes, translate("gettextFromC", "multiple GPS locations for this dive site; also %s\n"), coords); - free((void *)coords); - } - ds->latitude = latitude; - ds->longitude = longitude; - } - -} - -static void parse_dive_location(char *line, struct membuffer *str, void *_dive) -{ - (void) line; - uint32_t uuid; - char *name = get_utf8(str); - struct dive *dive = _dive; - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds) { - uuid = get_dive_site_uuid_by_name(name, NULL); - if (!uuid) - uuid = create_dive_site(name, dive->when); - dive->dive_site_uuid = uuid; - } else { - // we already had a dive site linked to the dive - if (same_string(ds->name, "")) { - ds->name = strdup(name); - } else { - // and that dive site had a name. that's weird - if our name is different, add it to the notes - if (!same_string(ds->name, name)) - ds->notes = add_to_string(ds->notes, translate("gettextFromC", "additional name for site: %s\n"), name); - } - } - free(name); -} - -static void parse_dive_divemaster(char *line, struct membuffer *str, void *_dive) -{ (void) line; struct dive *dive = _dive; dive->divemaster = get_utf8(str); } - -static void parse_dive_buddy(char *line, struct membuffer *str, void *_dive) -{ (void) line; struct dive *dive = _dive; dive->buddy = get_utf8(str); } - -static void parse_dive_suit(char *line, struct membuffer *str, void *_dive) -{ (void) line; struct dive *dive = _dive; dive->suit = get_utf8(str); } - -static void parse_dive_notes(char *line, struct membuffer *str, void *_dive) -{ (void) line; struct dive *dive = _dive; dive->notes = get_utf8(str); } - -static void parse_dive_divesiteid(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->dive_site_uuid = get_hex(line); } - -/* - * We can have multiple tags in the membuffer. They are separated by - * NUL bytes. - */ -static void parse_dive_tags(char *line, struct membuffer *str, void *_dive) -{ - (void) line; - struct dive *dive = _dive; - const char *tag; - int len = str->len; - - if (!len) - return; - - /* Make sure there is a NUL at the end too */ - tag = mb_cstring(str); - for (;;) { - int taglen = strlen(tag); - if (taglen) - taglist_add_tag(&dive->tag_list, tag); - len -= taglen; - if (!len) - return; - tag += taglen+1; - len--; - } -} - -static void parse_dive_airtemp(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->airtemp = get_temperature(line); } - -static void parse_dive_watertemp(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->watertemp = get_temperature(line); } - -static void parse_dive_duration(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->duration = get_duration(line); } - -static void parse_dive_rating(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->rating = get_index(line); } - -static void parse_dive_visibility(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->visibility = get_index(line); } - -static void parse_dive_notrip(char *line, struct membuffer *str, void *_dive) -{ - (void) str; - (void) line; - struct dive *dive = _dive; dive->tripflag = NO_TRIP; -} - -static void parse_site_description(char *line, struct membuffer *str, void *_ds) -{ (void) line; struct dive_site *ds = _ds; ds->description = strdup(mb_cstring(str)); } - -static void parse_site_name(char *line, struct membuffer *str, void *_ds) -{ (void) line; struct dive_site *ds = _ds; ds->name = strdup(mb_cstring(str)); } - -static void parse_site_notes(char *line, struct membuffer *str, void *_ds) -{ (void) line; struct dive_site *ds = _ds; ds->notes = strdup(mb_cstring(str)); } - -extern degrees_t parse_degrees(char *buf, char **end); -static void parse_site_gps(char *line, struct membuffer *str, void *_ds) -{ - (void) str; - struct dive_site *ds = _ds; - - ds->latitude = parse_degrees(line, &line); - ds->longitude = parse_degrees(line, &line); -} - -static void parse_site_geo(char *line, struct membuffer *str, void *_ds) -{ - struct dive_site *ds = _ds; - if (ds->taxonomy.category == NULL) - ds->taxonomy.category = alloc_taxonomy(); - int nr = ds->taxonomy.nr; - if (nr < TC_NR_CATEGORIES) { - struct taxonomy *t = &ds->taxonomy.category[nr]; - t->value = strdup(mb_cstring(str)); - sscanf(line, "cat %d origin %d \"", &t->category, (int *)&t->origin); - ds->taxonomy.nr++; - } -} - -/* Parse key=val parts of samples and cylinders etc */ -static char *parse_keyvalue_entry(void (*fn)(void *, const char *, const char *), void *fndata, char *line) -{ - char *key = line, *val, c; - - while ((c = *line) != 0) { - if (isspace(c) || c == '=') - break; - line++; - } - - if (c == '=') - *line++ = 0; - val = line; - - while ((c = *line) != 0) { - if (isspace(c)) - break; - line++; - } - if (c) - *line++ = 0; - - fn(fndata, key, val); - return line; -} - -static int cylinder_index, weightsystem_index; - -static void parse_cylinder_keyvalue(void *_cylinder, const char *key, const char *value) -{ - cylinder_t *cylinder = _cylinder; - if (!strcmp(key, "vol")) { - cylinder->type.size = get_volume(value); - return; - } - if (!strcmp(key, "workpressure")) { - cylinder->type.workingpressure = get_pressure(value); - return; - } - /* This is handled by the "get_utf8()" */ - if (!strcmp(key, "description")) - return; - if (!strcmp(key, "o2")) { - cylinder->gasmix.o2 = get_fraction(value); - return; - } - if (!strcmp(key, "he")) { - cylinder->gasmix.he = get_fraction(value); - return; - } - if (!strcmp(key, "start")) { - cylinder->start = get_pressure(value); - return; - } - if (!strcmp(key, "end")) { - cylinder->end = get_pressure(value); - return; - } - if (!strcmp(key, "use")) { - cylinder->cylinder_use = cylinderuse_from_text(value); - return; - } - report_error("Unknown cylinder key/value pair (%s/%s)", key, value); -} - -static void parse_dive_cylinder(char *line, struct membuffer *str, void *_dive) -{ - struct dive *dive = _dive; - cylinder_t *cylinder = dive->cylinder + cylinder_index; - - cylinder_index++; - cylinder->type.description = get_utf8(str); - for (;;) { - char c; - while (isspace(c = *line)) - line++; - if (!c) - break; - line = parse_keyvalue_entry(parse_cylinder_keyvalue, cylinder, line); - } -} - -static void parse_weightsystem_keyvalue(void *_ws, const char *key, const char *value) -{ - weightsystem_t *ws = _ws; - if (!strcmp(key, "weight")) { - ws->weight = get_weight(value); - return; - } - /* This is handled by the "get_utf8()" */ - if (!strcmp(key, "description")) - return; - report_error("Unknown weightsystem key/value pair (%s/%s)", key, value); -} - -static void parse_dive_weightsystem(char *line, struct membuffer *str, void *_dive) -{ - struct dive *dive = _dive; - weightsystem_t *ws = dive->weightsystem + weightsystem_index; - - weightsystem_index++; - ws->description = get_utf8(str); - for (;;) { - char c; - while (isspace(c = *line)) - line++; - if (!c) - break; - line = parse_keyvalue_entry(parse_weightsystem_keyvalue, ws, line); - } -} - -static int match_action(char *line, struct membuffer *str, void *data, - struct keyword_action *action, unsigned nr_action) -{ - char *p = line, c; - unsigned low, high; - - while ((c = *p) >= 'a' && c <= 'z') // skip over 1st word - p++; // Extract the second word from the line: - if (p == line) - return -1; - switch (c) { - case 0: // if 2nd word is C-terminated - break; - case ' ': // =end of 2nd word? - *p++ = 0; // then C-terminate that word - break; - default: - return -1; - } - - /* Standard binary search in a table */ - low = 0; - high = nr_action; - while (low < high) { - unsigned mid = (low+high)/2; - struct keyword_action *a = action + mid; - int cmp = strcmp(line, a->keyword); - if (!cmp) { // attribute found: - a->fn(p, str, data); // Execute appropriate function, - return 0; // .. passing 2n word from above - } // (p) as a function argument. - if (cmp < 0) - high = mid; - else - low = mid+1; - } -report_error("Unmatched action '%s'", line); - return -1; -} - -/* FIXME! We should do the array thing here too. */ -static void parse_sample_keyvalue(void *_sample, const char *key, const char *value) -{ - struct sample *sample = _sample; - - if (!strcmp(key, "sensor")) { - sample->sensor = atoi(value); - return; - } - if (!strcmp(key, "ndl")) { - sample->ndl = get_duration(value); - return; - } - if (!strcmp(key, "tts")) { - sample->tts = get_duration(value); - return; - } - if (!strcmp(key, "in_deco")) { - sample->in_deco = atoi(value); - return; - } - if (!strcmp(key, "stoptime")) { - sample->stoptime = get_duration(value); - return; - } - if (!strcmp(key, "stopdepth")) { - sample->stopdepth = get_depth(value); - return; - } - if (!strcmp(key, "cns")) { - sample->cns = atoi(value); - return; - } - - if (!strcmp(key, "rbt")) { - sample->rbt = get_duration(value); - return; - } - - if (!strcmp(key, "po2")) { - pressure_t p = get_pressure(value); - sample->setpoint.mbar = p.mbar; - return; - } - if (!strcmp(key, "sensor1")) { - pressure_t p = get_pressure(value); - sample->o2sensor[0].mbar = p.mbar; - return; - } - if (!strcmp(key, "sensor2")) { - pressure_t p = get_pressure(value); - sample->o2sensor[1].mbar = p.mbar; - return; - } - if (!strcmp(key, "sensor3")) { - pressure_t p = get_pressure(value); - sample->o2sensor[2].mbar = p.mbar; - return; - } - if (!strcmp(key, "o2pressure")) { - pressure_t p = get_pressure(value); - sample->o2cylinderpressure.mbar = p.mbar; - return; - } - if (!strcmp(key, "heartbeat")) { - sample->heartbeat = atoi(value); - return; - } - if (!strcmp(key, "bearing")) { - sample->bearing.degrees = atoi(value); - return; - } - - report_error("Unexpected sample key/value pair (%s/%s)", key, value); -} - -static char *parse_sample_unit(struct sample *sample, double val, char *unit) -{ - char *end = unit, c; - - /* Skip over the unit */ - while ((c = *end) != 0) { - if (isspace(c)) { - *end++ = 0; - break; - } - end++; - } - - /* The units are "°C", "m" or "bar", so let's just look at the first character */ - switch (*unit) { - case 'm': - sample->depth.mm = rint(1000*val); - break; - case 'b': - sample->cylinderpressure.mbar = rint(1000*val); - break; - default: - sample->temperature.mkelvin = C_to_mkelvin(val); - break; - } - - return end; -} - -/* - * By default the sample data does not change unless the - * save-file gives an explicit new value. So we copy the - * data from the previous sample if one exists, and then - * the parsing will update it as necessary. - * - * There are a few exceptions, like the sample pressure: - * missing sample pressure doesn't mean "same as last - * time", but "interpolate". We clear those ones - * explicitly. - */ -static struct sample *new_sample(struct divecomputer *dc) -{ - struct sample *sample = prepare_sample(dc); - if (sample != dc->sample) { - memcpy(sample, sample-1, sizeof(struct sample)); - sample->cylinderpressure.mbar = 0; - } - return sample; -} - -static void sample_parser(char *line, struct divecomputer *dc) -{ - int m, s = 0; - struct sample *sample = new_sample(dc); - - m = strtol(line, &line, 10); - if (*line == ':') - s = strtol(line+1, &line, 10); - sample->time.seconds = m*60+s; - - for (;;) { - char c; - - while (isspace(c = *line)) - line++; - if (!c) - break; - /* Less common sample entries have a name */ - if (c >= 'a' && c <= 'z') { - line = parse_keyvalue_entry(parse_sample_keyvalue, sample, line); - } else { - const char *end; - double val = ascii_strtod(line, &end); - if (end == line) { - report_error("Odd sample data: %s", line); - break; - } - line = (char *)end; - line = parse_sample_unit(sample, val, line); - } - } - finish_sample(dc); -} - -static void parse_dc_airtemp(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->airtemp = get_temperature(line); } - -static void parse_dc_date(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; update_date(&dc->when, line); } - -static void parse_dc_deviceid(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->deviceid = get_hex(line); } - -static void parse_dc_diveid(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->diveid = get_hex(line); } - -static void parse_dc_duration(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->duration = get_duration(line); } - -static void parse_dc_dctype(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->divemode = get_dctype(line); } - -static void parse_dc_maxdepth(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->maxdepth = get_depth(line); } - -static void parse_dc_meandepth(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->meandepth = get_depth(line); } - -static void parse_dc_model(char *line, struct membuffer *str, void *_dc) -{ (void) line; struct divecomputer *dc = _dc; dc->model = get_utf8(str); } - -static void parse_dc_numberofoxygensensors(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->no_o2sensors = get_index(line); } - -static void parse_dc_surfacepressure(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->surface_pressure = get_pressure(line); } - -static void parse_dc_salinity(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->salinity = get_salinity(line); } - -static void parse_dc_surfacetime(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->surfacetime = get_duration(line); } - -static void parse_dc_time(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; update_time(&dc->when, line); } - -static void parse_dc_watertemp(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->watertemp = get_temperature(line); } - -static void parse_event_keyvalue(void *_event, const char *key, const char *value) -{ - struct event *event = _event; - int val = atoi(value); - - if (!strcmp(key, "type")) { - event->type = val; - } else if (!strcmp(key, "flags")) { - event->flags = val; - } else if (!strcmp(key, "value")) { - event->value = val; - } else if (!strcmp(key, "name")) { - /* We get the name from the string handling */ - } else if (!strcmp(key, "cylinder")) { - /* NOTE! We add one here as a marker that "yes, we got a cylinder index" */ - event->gas.index = 1+get_index(value); - } else if (!strcmp(key, "o2")) { - event->gas.mix.o2 = get_fraction(value); - } else if (!strcmp(key, "he")) { - event->gas.mix.he = get_fraction(value); - } else - report_error("Unexpected event key/value pair (%s/%s)", key, value); -} - -/* keyvalue "key" "value" - * so we have two strings (possibly empty) in the membuffer, separated by a '\0' */ -static void parse_dc_keyvalue(char *line, struct membuffer *str, void *_dc) -{ - const char *key, *value; - struct divecomputer *dc = _dc; - - // Let's make sure we have two strings... - int string_counter = 0; - while(*line) { - if (*line == '"') - string_counter++; - line++; - } - if (string_counter != 2) - return; - - // stupidly the second string in the membuffer isn't NUL terminated; - // asking for a cstring fixes that; interestingly enough, given that there are two - // strings in the mb, the next command at the same time assigns a pointer to the - // first string to 'key' and NUL terminates the second string (which then goes to 'value') - key = mb_cstring(str); - value = key + strlen(key) + 1; - add_extra_data(dc, key, value); -} - -static void parse_dc_event(char *line, struct membuffer *str, void *_dc) -{ - int m, s = 0; - const char *name; - struct divecomputer *dc = _dc; - struct event event = { 0 }, *ev; - - m = strtol(line, &line, 10); - if (*line == ':') - s = strtol(line+1, &line, 10); - event.time.seconds = m*60+s; - - for (;;) { - char c; - while (isspace(c = *line)) - line++; - if (!c) - break; - line = parse_keyvalue_entry(parse_event_keyvalue, &event, line); - } - - name = ""; - if (str->len) - name = mb_cstring(str); - ev = add_event(dc, event.time.seconds, event.type, event.flags, event.value, name); - - /* - * Older logs might mark the dive to be CCR by having an "SP change" event at time 0:00. - * Better to mark them being CCR on import so no need for special treatments elsewhere on - * the code. - */ - if (ev && event.time.seconds == 0 && event.type == SAMPLE_EVENT_PO2 && dc->divemode==OC) { - dc->divemode = CCR; - } - - if (ev && event_is_gaschange(ev)) { - /* - * We subtract one here because "0" is "no index", - * and the parsing will add one for actual cylinder - * index data (see parse_event_keyvalue) - */ - ev->gas.index = event.gas.index-1; - if (event.gas.mix.o2.permille || event.gas.mix.he.permille) - ev->gas.mix = event.gas.mix; - } -} - -static void parse_trip_date(char *line, struct membuffer *str, void *_trip) -{ (void) str; dive_trip_t *trip = _trip; update_date(&trip->when, line); } - -static void parse_trip_time(char *line, struct membuffer *str, void *_trip) -{ (void) str; dive_trip_t *trip = _trip; update_time(&trip->when, line); } - -static void parse_trip_location(char *line, struct membuffer *str, void *_trip) -{ (void) line; dive_trip_t *trip = _trip; trip->location = get_utf8(str); } - -static void parse_trip_notes(char *line, struct membuffer *str, void *_trip) -{ (void) line; dive_trip_t *trip = _trip; trip->notes = get_utf8(str); } - -static void parse_settings_autogroup(char *line, struct membuffer *str, void *_unused) -{ - (void) line; - (void) str; - (void) _unused; - set_autogroup(1); -} - -static void parse_settings_units(char *line, struct membuffer *str, void *unused) -{ - (void) str; - (void) unused; - if (line) - set_informational_units(line); -} - -static void parse_settings_userid(char *line, struct membuffer *str, void *_unused) -{ - (void) str; - (void) _unused; - if (line) { - set_save_userid_local(true); - set_userid(line); - } -} - -/* - * Our versioning is a joke right now, but this is more of an example of what we - * *can* do some day. And if we do change the version, this warning will show if - * you read with a version of subsurface that doesn't know about it. - * We MUST keep this in sync with the XML version (so we can report a consistent - * minimum datafile version) - */ -static void parse_settings_version(char *line, struct membuffer *str, void *_unused) -{ - (void) str; - (void) _unused; - int version = atoi(line); - report_datafile_version(version); - if (version > DATAFORMAT_VERSION) - report_error("Git save file version %d is newer than version %d I know about", version, DATAFORMAT_VERSION); -} - -/* The string in the membuffer is the version string of subsurface that saved things, just FYI */ -static void parse_settings_subsurface(char *line, struct membuffer *str, void *_unused) -{ - (void) line; - (void) str; - (void) _unused; -} - -struct divecomputerid { - const char *model; - const char *nickname; - const char *firmware; - const char *serial; - const char *cstr; - unsigned int deviceid; -}; - -static void parse_divecomputerid_keyvalue(void *_cid, const char *key, const char *value) -{ - struct divecomputerid *cid = _cid; - - if (*value == '"') { - value = cid->cstr; - cid->cstr += strlen(cid->cstr)+1; - } - if (!strcmp(key, "deviceid")) { - cid->deviceid = get_hex(value); - return; - } - if (!strcmp(key, "serial")) { - cid->serial = value; - return; - } - if (!strcmp(key, "firmware")) { - cid->firmware = value; - return; - } - if (!strcmp(key, "nickname")) { - cid->nickname = value; - return; - } - report_error("Unknow divecomputerid key/value pair (%s/%s)", key, value); -} - -/* - * The 'divecomputerid' is a bit harder to parse than some other things, because - * it can have multiple strings (but see the tag parsing for another example of - * that) in addition to the non-string entries. - * - * We keep the "next" string in "id.cstr" and update it as we use it. - */ -static void parse_settings_divecomputerid(char *line, struct membuffer *str, void *_unused) -{ - (void) _unused; - struct divecomputerid id = { mb_cstring(str) }; - - id.cstr = id.model + strlen(id.model) + 1; - - /* Skip the '"' that stood for the model string */ - line++; - - for (;;) { - char c; - while (isspace(c = *line)) - line++; - if (!c) - break; - line = parse_keyvalue_entry(parse_divecomputerid_keyvalue, &id, line); - } - create_device_node(id.model, id.deviceid, id.serial, id.firmware, id.nickname); -} - -static void parse_picture_filename(char *line, struct membuffer *str, void *_pic) -{ - (void) line; - struct picture *pic = _pic; - pic->filename = get_utf8(str); -} - -static void parse_picture_gps(char *line, struct membuffer *str, void *_pic) -{ - (void) str; - struct picture *pic = _pic; - - pic->latitude = parse_degrees(line, &line); - pic->longitude = parse_degrees(line, &line); -} - -static void parse_picture_hash(char *line, struct membuffer *str, void *_pic) -{ - (void) line; - struct picture *pic = _pic; - pic->hash = get_utf8(str); -} - -/* These need to be sorted! */ -struct keyword_action dc_action[] = { -#undef D -#define D(x) { #x, parse_dc_ ## x } - D(airtemp), D(date), D(dctype), D(deviceid), D(diveid), D(duration), - D(event), D(keyvalue), D(maxdepth), D(meandepth), D(model), D(numberofoxygensensors), - D(salinity), D(surfacepressure), D(surfacetime), D(time), D(watertemp) -}; - -/* Sample lines start with a space or a number */ -static void divecomputer_parser(char *line, struct membuffer *str, void *_dc) -{ - char c = *line; - if (c < 'a' || c > 'z') - sample_parser(line, _dc); - match_action(line, str, _dc, dc_action, ARRAY_SIZE(dc_action)); -} - -/* These need to be sorted! */ -struct keyword_action dive_action[] = { -#undef D -#define D(x) { #x, parse_dive_ ## x } - D(airtemp), D(buddy), D(cylinder), D(divemaster), D(divesiteid), D(duration), - D(gps), D(location), D(notes), D(notrip), D(rating), D(suit), - D(tags), D(visibility), D(watertemp), D(weightsystem) -}; - -static void dive_parser(char *line, struct membuffer *str, void *_dive) -{ - match_action(line, str, _dive, dive_action, ARRAY_SIZE(dive_action)); -} - -/* These need to be sorted! */ -struct keyword_action site_action[] = { -#undef D -#define D(x) { #x, parse_site_ ## x } - D(description), D(geo), D(gps), D(name), D(notes) -}; - -static void site_parser(char *line, struct membuffer *str, void *_ds) -{ - match_action(line, str, _ds, site_action, ARRAY_SIZE(site_action)); -} - -/* These need to be sorted! */ -struct keyword_action trip_action[] = { -#undef D -#define D(x) { #x, parse_trip_ ## x } - D(date), D(location), D(notes), D(time), -}; - -static void trip_parser(char *line, struct membuffer *str, void *_trip) -{ - match_action(line, str, _trip, trip_action, ARRAY_SIZE(trip_action)); -} - -/* These need to be sorted! */ -static struct keyword_action settings_action[] = { -#undef D -#define D(x) { #x, parse_settings_ ## x } - D(autogroup), D(divecomputerid), D(subsurface), D(units), D(userid), D(version), -}; - -static void settings_parser(char *line, struct membuffer *str, void *_unused) -{ - (void) _unused; - match_action(line, str, NULL, settings_action, ARRAY_SIZE(settings_action)); -} - -/* These need to be sorted! */ -static struct keyword_action picture_action[] = { -#undef D -#define D(x) { #x, parse_picture_ ## x } - D(filename), D(gps), D(hash) -}; - -static void picture_parser(char *line, struct membuffer *str, void *_pic) -{ - match_action(line, str, _pic, picture_action, ARRAY_SIZE(picture_action)); -} - -/* - * We have a very simple line-based interface, with the small - * complication that lines can have strings in the middle, and - * a string can be multiple lines. - * - * The UTF-8 string escaping is *very* simple, though: - * - * - a string starts and ends with double quotes (") - * - * - inside the string we escape: - * (a) double quotes with '\"' - * (b) backslash (\) with '\\' - * - * - additionally, for human readability, we escape - * newlines with '\n\t', with the exception that - * consecutive newlines are left unescaped (so an - * empty line doesn't become a line with just a tab - * on it). - * - * Also, while the UTF-8 string can have arbitrarily - * long lines, the non-string parts of the lines are - * never long, so we can use a small temporary buffer - * on stack for that part. - * - * Also, note that if a line has one or more strings - * in it: - * - * - each string will be represented as a single '"' - * character in the output. - * - * - all string will exist in the same 'membuffer', - * separated by NUL characters (that cannot exist - * in a string, not even quoted). - */ -static const char *parse_one_string(const char *buf, const char *end, struct membuffer *b) -{ - const char *p = buf; - - /* - * We turn multiple strings one one line (think dive tags) into one - * membuffer that has NUL characters in between strings. - */ - if (b->len) - put_bytes(b, "", 1); - - while (p < end) { - char replace; - - switch (*p++) { - default: - continue; - case '\n': - if (p < end && *p == '\t') { - replace = '\n'; - break; - } - continue; - case '\\': - if (p < end) { - replace = *p; - break; - } - continue; - case '"': - replace = 0; - break; - } - put_bytes(b, buf, p - buf - 1); - if (!replace) - break; - put_bytes(b, &replace, 1); - buf = ++p; - } - return p; -} - -typedef void (line_fn_t)(char *, struct membuffer *, void *); -#define MAXLINE 500 -static unsigned parse_one_line(const char *buf, unsigned size, line_fn_t *fn, void *fndata, struct membuffer *b) -{ - const char *end = buf + size; - const char *p = buf; - char line[MAXLINE+1]; - int off = 0; - - while (p < end) { - char c = *p++; - if (c == '\n') - break; - line[off] = c; - off++; - if (off > MAXLINE) - off = MAXLINE; - if (c == '"') - p = parse_one_string(p, end, b); - } - line[off] = 0; - fn(line, b, fndata); - return p - buf; -} - -/* - * We keep on re-using the membuffer that we use for - * strings, but the callback function can "steal" it by - * saving its value and just clear the original. - */ -static void for_each_line(git_blob *blob, line_fn_t *fn, void *fndata) -{ - const char *content = git_blob_rawcontent(blob); - unsigned int size = git_blob_rawsize(blob); - struct membuffer str = { 0 }; - - while (size) { - unsigned int n = parse_one_line(content, size, fn, fndata, &str); - content += n; - size -= n; - - /* Re-use the allocation, but forget the data */ - str.len = 0; - } - free_buffer(&str); -} - -#define GIT_WALK_OK 0 -#define GIT_WALK_SKIP 1 - -static struct divecomputer *active_dc; -static struct dive *active_dive; -static dive_trip_t *active_trip; - -static void finish_active_trip(void) -{ - dive_trip_t *trip = active_trip; - - if (trip) { - active_trip = NULL; - insert_trip(&trip); - } -} - -static void finish_active_dive(void) -{ - struct dive *dive = active_dive; - - if (dive) { - /* check if we need to save pictures */ - FOR_EACH_PICTURE(dive) { - if (!picture_exists(picture)) - save_picture_from_git(picture); - } - /* free any memory we allocated to track pictures */ - while (pel) { - free(pel->data); - void *lastone = pel; - pel = pel->next; - free(lastone); - } - active_dive = NULL; - record_dive(dive); - } -} - -static struct dive *create_new_dive(timestamp_t when) -{ - struct dive *dive = alloc_dive(); - - /* We'll fill in more data from the dive file */ - dive->when = when; - - if (active_trip) - add_dive_to_trip(dive, active_trip); - return dive; -} - -static dive_trip_t *create_new_trip(int yyyy, int mm, int dd) -{ - dive_trip_t *trip = calloc(1, sizeof(dive_trip_t)); - struct tm tm = { 0 }; - - /* We'll fill in the real data from the trip descriptor file */ - tm.tm_year = yyyy; - tm.tm_mon = mm-1; - tm.tm_mday = dd; - trip->when = utc_mktime(&tm); - - return trip; -} - -static bool validate_date(int yyyy, int mm, int dd) -{ - return yyyy > 1970 && yyyy < 3000 && - mm > 0 && mm < 13 && - dd > 0 && dd < 32; -} - -static bool validate_time(int h, int m, int s) -{ - return h >= 0 && h < 24 && - m >= 0 && m < 60 && - s >=0 && s <= 60; -} - -/* - * Dive trip directory, name is 'nn-alphabetic[~hex]' - */ -static int dive_trip_directory(const char *root, const char *name) -{ - int yyyy = -1, mm = -1, dd = -1; - - if (sscanf(root, "%d/%d", &yyyy, &mm) != 2) - return GIT_WALK_SKIP; - dd = atoi(name); - if (!validate_date(yyyy, mm, dd)) - return GIT_WALK_SKIP; - finish_active_trip(); - active_trip = create_new_trip(yyyy, mm, dd); - return GIT_WALK_OK; -} - -/* - * Dive directory, name is [[yyyy-]mm-]nn-ddd-hh:mm:ss[~hex] in older git repositories - * but [[yyyy-]mm-]nn-ddd-hh=mm=ss[~hex] in newer repos as ':' is an illegal character for Windows files - * and 'timeoff' points to what should be the time part of - * the name (the first digit of the hour). - * - * The root path will be of the form yyyy/mm[/tripdir], - */ -static int dive_directory(const char *root, const git_tree_entry *entry, const char *name, int timeoff) -{ - int yyyy = -1, mm = -1, dd = -1; - int h, m, s; - int mday_off, month_off, year_off; - struct tm tm; - - /* Skip the '-' before the time */ - mday_off = timeoff; - if (!mday_off || name[--mday_off] != '-') - return GIT_WALK_SKIP; - /* Skip the day name */ - while (mday_off > 0 && name[--mday_off] != '-') - /* nothing */; - - mday_off = mday_off - 2; - month_off = mday_off - 3; - year_off = month_off - 5; - if (mday_off < 0) - return GIT_WALK_SKIP; - - /* Get the time of day -- parse both time formats so we can read old repos when not on Windows */ - if (sscanf(name+timeoff, "%d:%d:%d", &h, &m, &s) != 3 && sscanf(name+timeoff, "%d=%d=%d", &h, &m, &s) != 3) - return GIT_WALK_SKIP; - if (!validate_time(h, m, s)) - return GIT_WALK_SKIP; - - /* - * Using the "git_tree_walk()" interface is simple, but - * it kind of sucks as an interface because there is - * no sane way to pass the hierarchy to the callbacks. - * The "payload" is a fixed one-time thing: we'd like - * the "current trip" to be passed down to the dives - * that get parsed under that trip, but we can't. - * - * So "active_trip" is not the trip that is in the hierarchy - * _above_ us, it's just the trip that was _before_ us. But - * if a dive is not in a trip at all, we can't tell. - * - * We could just do a better walker that passes the - * return value around, but we hack around this by - * instead looking at the one hierarchical piece of - * data we have: the pathname to the current entry. - * - * This is pretty hacky. The magic '8' is the length - * of a pathname of the form 'yyyy/mm/'. - */ - if (strlen(root) == 8) - finish_active_trip(); - - /* - * Get the date. The day of the month is in the dive directory - * name, the year and month might be in the path leading up - * to it. - */ - dd = atoi(name + mday_off); - if (year_off < 0) { - if (sscanf(root, "%d/%d", &yyyy, &mm) != 2) - return GIT_WALK_SKIP; - } else - yyyy = atoi(name + year_off); - if (month_off >= 0) - mm = atoi(name + month_off); - - if (!validate_date(yyyy, mm, dd)) - return GIT_WALK_SKIP; - - /* Ok, close enough. We've gotten sufficient information */ - memset(&tm, 0, sizeof(tm)); - tm.tm_hour = h; - tm.tm_min = m; - tm.tm_sec = s; - tm.tm_year = yyyy - 1900; - tm.tm_mon = mm-1; - tm.tm_mday = dd; - - finish_active_dive(); - active_dive = create_new_dive(utc_mktime(&tm)); - memcpy(active_dive->git_id, git_tree_entry_id(entry)->id, 20); - return GIT_WALK_OK; -} - -static int picture_directory(const char *root, const char *name) -{ - (void) root; - (void) name; - if (!active_dive) - return GIT_WALK_SKIP; - return GIT_WALK_OK; -} - -/* - * Return the length of the string without the unique part. - */ -static int nonunique_length(const char *str) -{ - int len = 0; - - for (;;) { - char c = *str++; - if (!c || c == '~') - return len; - len++; - } -} - -/* - * When hitting a directory node, we have a couple of cases: - * - * - It's just a date entry - all numeric (either year or month): - * - * [yyyy|mm] - * - * We don't do anything with these, we just traverse into them. - * The numeric data will show up as part of the full path when - * we hit more interesting entries. - * - * - It's a trip directory. The name will be of the form - * - * nn-alphabetic[~hex] - * - * where 'nn' is the day of the month (year and month will be - * encoded in the path leading up to this). - * - * - It's a dive directory. The name will be of the form - * - * [[yyyy-]mm-]nn-ddd-hh=mm=ss[~hex] - * - * (older versions had this as [[yyyy-]mm-]nn-ddd-hh:mm:ss[~hex] - * but that faile on Windows) - * - * which describes the date and time of a dive (yyyy and mm - * are optional, and may be encoded in the path leading up to - * the dive). - * - * - It is a per-dive picture directory ("Pictures") - * - * - It's some random non-dive-data directory. - * - * If it doesn't match the above patterns, we'll ignore them - * for dive loading purposes, and not even recurse into them. - */ -static int walk_tree_directory(const char *root, const git_tree_entry *entry) -{ - const char *name = git_tree_entry_name(entry); - int digits = 0, len; - char c; - - if (!strcmp(name, "Pictures")) - return picture_directory(root, name); - - if (!strcmp(name, "01-Divesites")) - return GIT_WALK_OK; - - while (isdigit(c = name[digits])) - digits++; - - /* Doesn't start with two or four digits? Skip */ - if (digits != 4 && digits != 2) - return GIT_WALK_SKIP; - - /* Only digits? Do nothing, but recurse into it */ - if (!c) - return GIT_WALK_OK; - - /* All valid cases need to have a slash following */ - if (c != '-') - return GIT_WALK_SKIP; - - /* Do a quick check for a common dive case */ - len = nonunique_length(name); - - /* - * We know the len is at least 3, because we had at least - * two digits and a dash - */ - if (name[len-3] == ':' || name[len-3] == '=') - return dive_directory(root, entry, name, len-8); - - if (digits != 2) - return GIT_WALK_SKIP; - - return dive_trip_directory(root, name); -} - -git_blob *git_tree_entry_blob(git_repository *repo, const git_tree_entry *entry) -{ - const git_oid *id = git_tree_entry_id(entry); - git_blob *blob; - - if (git_blob_lookup(&blob, repo, id)) - return NULL; - return blob; -} - -static struct divecomputer *create_new_dc(struct dive *dive) -{ - struct divecomputer *dc = &dive->dc; - - while (dc->next) - dc = dc->next; - /* Did we already fill that in? */ - if (dc->samples || dc->model || dc->when) { - struct divecomputer *newdc = calloc(1, sizeof(*newdc)); - if (!newdc) - return NULL; - dc->next = newdc; - dc = newdc; - } - dc->when = dive->when; - dc->duration = dive->duration; - return dc; -} - -/* - * We should *really* try to delay the dive computer data parsing - * until necessary, in order to reduce load-time. The parsing is - * cheap, but the loading of the git blob into memory can be pretty - * costly. - */ -static int parse_divecomputer_entry(git_repository *repo, const git_tree_entry *entry, const char *suffix) -{ - (void) suffix; - git_blob *blob = git_tree_entry_blob(repo, entry); - - if (!blob) - return report_error("Unable to read divecomputer file"); - - active_dc = create_new_dc(active_dive); - for_each_line(blob, divecomputer_parser, active_dc); - git_blob_free(blob); - active_dc = NULL; - return 0; -} - -/* - * NOTE! The "git_id" for the dive is the hash for the whole dive directory. - * As such, it covers not just the dive, but the divecomputers and the - * pictures too. So if any of the dive computers change, the dive cache - * has to be invalidated too. - */ -static int parse_dive_entry(git_repository *repo, const git_tree_entry *entry, const char *suffix) -{ - struct dive *dive = active_dive; - git_blob *blob = git_tree_entry_blob(repo, entry); - if (!blob) - return report_error("Unable to read dive file"); - if (*suffix) - dive->number = atoi(suffix+1); - cylinder_index = weightsystem_index = 0; - for_each_line(blob, dive_parser, active_dive); - git_blob_free(blob); - return 0; -} - -static int parse_site_entry(git_repository *repo, const git_tree_entry *entry, const char *suffix) -{ - if (*suffix == '\0') - return report_error("Dive site without uuid"); - uint32_t uuid = strtoul(suffix, NULL, 16); - struct dive_site *ds = alloc_or_get_dive_site(uuid); - git_blob *blob = git_tree_entry_blob(repo, entry); - if (!blob) - return report_error("Unable to read dive site file"); - for_each_line(blob, site_parser, ds); - git_blob_free(blob); - return 0; -} - -static int parse_trip_entry(git_repository *repo, const git_tree_entry *entry) -{ - git_blob *blob = git_tree_entry_blob(repo, entry); - if (!blob) - return report_error("Unable to read trip file"); - for_each_line(blob, trip_parser, active_trip); - git_blob_free(blob); - return 0; -} - -static int parse_settings_entry(git_repository *repo, const git_tree_entry *entry) -{ - git_blob *blob = git_tree_entry_blob(repo, entry); - if (!blob) - return report_error("Unable to read settings file"); - set_save_userid_local(false); - for_each_line(blob, settings_parser, NULL); - git_blob_free(blob); - return 0; -} - -static int parse_picture_file(git_repository *repo, const git_tree_entry *entry, const char *name) -{ - /* remember the picture data so we can handle it when all dive data has been loaded - * the name of the git file is PIC-<hash> */ - git_blob *blob = git_tree_entry_blob(repo, entry); - const void *rawdata = git_blob_rawcontent(blob); - int len = git_blob_rawsize(blob); - struct picture_entry_list *new_pel = malloc(sizeof(struct picture_entry_list)); - new_pel->next = pel; - pel = new_pel; - pel->data = malloc(len); - memcpy(pel->data, rawdata, len); - pel->len = len; - pel->hash = strdup(name + 4); - git_blob_free(blob); - return 0; -} - -static int parse_picture_entry(git_repository *repo, const git_tree_entry *entry, const char *name) -{ - git_blob *blob; - struct picture *pic; - int hh, mm, ss, offset; - char sign; - - /* - * The format of the picture name files is just the offset within - * the dive in form [[+-]hh=mm=ss (previously [[+-]hh:mm:ss, but - * that didn't work on Windows), possibly followed by a hash to - * make the filename unique (which we can just ignore). - */ - if (sscanf(name, "%c%d:%d:%d", &sign, &hh, &mm, &ss) != 4 && - sscanf(name, "%c%d=%d=%d", &sign, &hh, &mm, &ss) != 4) - return report_error("Unknown file name %s", name); - offset = ss + 60*(mm + 60*hh); - if (sign == '-') - offset = -offset; - - blob = git_tree_entry_blob(repo, entry); - if (!blob) - return report_error("Unable to read picture file"); - - pic = alloc_picture(); - pic->offset.seconds = offset; - - for_each_line(blob, picture_parser, pic); - dive_add_picture(active_dive, pic); - git_blob_free(blob); - return 0; -} - -static int walk_tree_file(const char *root, const git_tree_entry *entry, git_repository *repo) -{ - struct dive *dive = active_dive; - dive_trip_t *trip = active_trip; - const char *name = git_tree_entry_name(entry); - if (verbose > 1) - fprintf(stderr, "git load handling file %s\n", name); - switch (*name) { - /* Picture file? They are saved as time offsets in the dive */ - case '-': case '+': - if (dive) - return parse_picture_entry(repo, entry, name); - break; - case 'D': - if (dive && !strncmp(name, "Divecomputer", 12)) - return parse_divecomputer_entry(repo, entry, name+12); - if (dive && !strncmp(name, "Dive", 4)) - return parse_dive_entry(repo, entry, name+4); - break; - case 'S': - if (!strncmp(name, "Site", 4)) - return parse_site_entry(repo, entry, name + 5); - break; - case '0': - if (trip && !strcmp(name, "00-Trip")) - return parse_trip_entry(repo, entry); - if (!strcmp(name, "00-Subsurface")) - return parse_settings_entry(repo, entry); - break; - case 'P': - if (dive && !strncmp(name, "PIC-", 4)) - return parse_picture_file(repo, entry, name); - break; - } - report_error("Unknown file %s%s (%p %p)", root, name, dive, trip); - return GIT_WALK_SKIP; -} - -static int walk_tree_cb(const char *root, const git_tree_entry *entry, void *payload) -{ - git_repository *repo = payload; - git_filemode_t mode = git_tree_entry_filemode(entry); - - if (mode == GIT_FILEMODE_TREE) - return walk_tree_directory(root, entry); - - walk_tree_file(root, entry, repo); - /* Ignore failed blob loads */ - return GIT_WALK_OK; -} - -static int load_dives_from_tree(git_repository *repo, git_tree *tree) -{ - git_tree_walk(tree, GIT_TREEWALK_PRE, walk_tree_cb, repo); - return 0; -} - -void clear_git_id(void) -{ - saved_git_id = NULL; -} - -void set_git_id(const struct git_oid * id) -{ - static char git_id_buffer[GIT_OID_HEXSZ+1]; - - git_oid_tostr(git_id_buffer, sizeof(git_id_buffer), id); - saved_git_id = git_id_buffer; -} - -static int find_commit(git_repository *repo, const char *branch, git_commit **commit_p) -{ - git_object *object; - - if (git_revparse_single(&object, repo, branch)) - return report_error("Unable to look up revision '%s'", branch); - if (git_object_peel((git_object **)commit_p, object, GIT_OBJ_COMMIT)) - return report_error("Revision '%s' is not a valid commit", branch); - return 0; -} - -static int do_git_load(git_repository *repo, const char *branch) -{ - int ret; - git_commit *commit; - git_tree *tree; - - ret = find_commit(repo, branch, &commit); - if (ret) - return ret; - if (git_commit_tree(&tree, commit)) - return report_error("Could not look up tree of commit in branch '%s'", branch); - ret = load_dives_from_tree(repo, tree); - if (!ret) - set_git_id(git_commit_id(commit)); - git_object_free((git_object *)tree); - return ret; -} - -const char *get_sha(git_repository *repo, const char *branch) -{ - static char git_id_buffer[GIT_OID_HEXSZ+1]; - git_commit *commit; - if (find_commit(repo, branch, &commit)) - return NULL; - git_oid_tostr(git_id_buffer, sizeof(git_id_buffer), (const git_oid *)commit); - return git_id_buffer; -} - -/* - * Like git_save_dives(), this silently returns a negative - * value if it's not a git repository at all (so that you - * can try to load it some other way. - * - * If it is a git repository, we return zero for success, - * or report an error and return 1 if the load failed. - */ -int git_load_dives(struct git_repository *repo, const char *branch) -{ - int ret; - - if (repo == dummy_git_repository) - return report_error("Unable to open git repository at '%s'", branch); - ret = do_git_load(repo, branch); - git_repository_free(repo); - free((void *)branch); - finish_active_dive(); - finish_active_trip(); - return ret; -} diff --git a/subsurface-core/macos.c b/subsurface-core/macos.c deleted file mode 100644 index 500412cd8..000000000 --- a/subsurface-core/macos.c +++ /dev/null @@ -1,218 +0,0 @@ -/* macos.c */ -/* implements Mac OS X specific functions */ -#include <stdlib.h> -#include <dirent.h> -#include <fnmatch.h> -#include "dive.h" -#include "display.h" -#include <CoreFoundation/CoreFoundation.h> -#if !defined(__IPHONE_5_0) -#include <CoreServices/CoreServices.h> -#endif -#include <mach-o/dyld.h> -#include <sys/syslimits.h> -#include <stdio.h> -#include <fcntl.h> -#include <unistd.h> - -void subsurface_user_info(struct user_info *info) -{ - (void) info; - /* Nothing, let's use libgit2-20 on MacOS */ -} - -/* macos defines CFSTR to create a CFString object from a constant, - * but no similar macros if a C string variable is supposed to be - * the argument. We add this here (hardcoding the default allocator - * and MacRoman encoding */ -#define CFSTR_VAR(_var) CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, \ - (_var), kCFStringEncodingMacRoman, \ - kCFAllocatorNull) - -#define SUBSURFACE_PREFERENCES CFSTR("org.hohndel.subsurface") -#define ICON_NAME "Subsurface.icns" -#define UI_FONT "Arial 12" - -const char mac_system_divelist_default_font[] = "Arial"; -const char *system_divelist_default_font = mac_system_divelist_default_font; -double system_divelist_default_font_size = -1.0; - -void subsurface_OS_pref_setup(void) -{ - // nothing -} - -bool subsurface_ignore_font(const char *font) -{ - (void) font; - // there are no old default fonts to ignore - return false; -} - -static const char *system_default_path_append(const char *append) -{ - const char *home = getenv("HOME"); - const char *path = "/Library/Application Support/Subsurface"; - - int len = strlen(home) + strlen(path) + 1; - if (append) - len += strlen(append) + 1; - - char *buffer = (char *)malloc(len); - memset(buffer, 0, len); - strcat(buffer, home); - strcat(buffer, path); - if (append) { - strcat(buffer, "/"); - strcat(buffer, append); - } - - return buffer; -} - -const char *system_default_directory(void) -{ - static const char *path = NULL; - if (!path) - path = system_default_path_append(NULL); - return path; -} - -const char *system_default_filename(void) -{ - static char *filename = NULL; - if (!filename) { - const char *user = getenv("LOGNAME"); - if (same_string(user, "")) - user = "username"; - filename = calloc(strlen(user) + 5, 1); - strcat(filename, user); - strcat(filename, ".xml"); - } - static const char *path = NULL; - if (!path) - path = system_default_path_append(filename); - return path; -} - -int enumerate_devices(device_callback_t callback, void *userdata, int dc_type) -{ - int index = -1, entries = 0; - DIR *dp = NULL; - struct dirent *ep = NULL; - size_t i; - if (dc_type != DC_TYPE_UEMIS) { - const char *dirname = "/dev"; - const char *patterns[] = { - "tty.*", - "usbserial", - NULL - }; - - dp = opendir(dirname); - if (dp == NULL) { - return -1; - } - - while ((ep = readdir(dp)) != NULL) { - for (i = 0; patterns[i] != NULL; ++i) { - if (fnmatch(patterns[i], ep->d_name, 0) == 0) { - char filename[1024]; - int n = snprintf(filename, sizeof(filename), "%s/%s", dirname, ep->d_name); - if (n >= (int)sizeof(filename)) { - closedir(dp); - return -1; - } - callback(filename, userdata); - if (is_default_dive_computer_device(filename)) - index = entries; - entries++; - break; - } - } - } - closedir(dp); - } - if (dc_type != DC_TYPE_SERIAL) { - const char *dirname = "/Volumes"; - int num_uemis = 0; - dp = opendir(dirname); - if (dp == NULL) { - return -1; - } - - while ((ep = readdir(dp)) != NULL) { - if (fnmatch("UEMISSDA", ep->d_name, 0) == 0) { - char filename[1024]; - int n = snprintf(filename, sizeof(filename), "%s/%s", dirname, ep->d_name); - if (n >= (int)sizeof(filename)) { - closedir(dp); - return -1; - } - callback(filename, userdata); - if (is_default_dive_computer_device(filename)) - index = entries; - entries++; - num_uemis++; - break; - } - } - closedir(dp); - if (num_uemis == 1 && entries == 1) /* if we find exactly one entry and that's a Uemis, select it */ - index = 0; - } - return index; -} - -/* NOP wrappers to comform with windows.c */ -int subsurface_rename(const char *path, const char *newpath) -{ - return rename(path, newpath); -} - -int subsurface_open(const char *path, int oflags, mode_t mode) -{ - return open(path, oflags, mode); -} - -FILE *subsurface_fopen(const char *path, const char *mode) -{ - return fopen(path, mode); -} - -void *subsurface_opendir(const char *path) -{ - return (void *)opendir(path); -} - -int subsurface_access(const char *path, int mode) -{ - return access(path, mode); -} - -struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp) -{ - return zip_open(path, flags, errorp); -} - -int subsurface_zip_close(struct zip *zip) -{ - return zip_close(zip); -} - -/* win32 console */ -void subsurface_console_init(bool dedicated) -{ - (void) dedicated; - /* NOP */ -} - -void subsurface_console_exit(void) -{ - /* NOP */ -} - -bool subsurface_user_is_root() -{ - return (geteuid() == 0); -} diff --git a/subsurface-core/membuffer.c b/subsurface-core/membuffer.c deleted file mode 100644 index 053edb8f0..000000000 --- a/subsurface-core/membuffer.c +++ /dev/null @@ -1,288 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include <stdarg.h> -#include <stdlib.h> -#include <string.h> -#include <stdio.h> - -#include "dive.h" -#include "membuffer.h" - -char *detach_buffer(struct membuffer *b) -{ - char *result = b->buffer; - b->buffer = NULL; - b->len = 0; - b->alloc = 0; - return result; -} - -void free_buffer(struct membuffer *b) -{ - free(detach_buffer(b)); -} - -void flush_buffer(struct membuffer *b, FILE *f) -{ - if (b->len) { - fwrite(b->buffer, 1, b->len, f); - free_buffer(b); - } -} - -void strip_mb(struct membuffer *b) -{ - while (b->len && isspace(b->buffer[b->len - 1])) - b->len--; -} - -/* - * Running out of memory isn't really an issue these days. - * So rather than do insane error handling and making the - * interface very complex, we'll just die. It won't happen - * unless you're running on a potato. - */ -static void oom(void) -{ - fprintf(stderr, "Out of memory\n"); - exit(1); -} - -static void make_room(struct membuffer *b, unsigned int size) -{ - unsigned int needed = b->len + size; - if (needed > b->alloc) { - char *n; - /* round it up to not reallocate all the time.. */ - needed = needed * 9 / 8 + 1024; - n = realloc(b->buffer, needed); - if (!n) - oom(); - b->buffer = n; - b->alloc = needed; - } -} - -const char *mb_cstring(struct membuffer *b) -{ - make_room(b, 1); - b->buffer[b->len] = 0; - return b->buffer; -} - -void put_bytes(struct membuffer *b, const char *str, int len) -{ - make_room(b, len); - memcpy(b->buffer + b->len, str, len); - b->len += len; -} - -void put_string(struct membuffer *b, const char *str) -{ - put_bytes(b, str, strlen(str)); -} - -void put_vformat(struct membuffer *b, const char *fmt, va_list args) -{ - int room = 128; - - for (;;) { - int len; - va_list copy; - char *target; - - make_room(b, room); - room = b->alloc - b->len; - target = b->buffer + b->len; - - va_copy(copy, args); - len = vsnprintf(target, room, fmt, copy); - va_end(copy); - - if (len < room) { - b->len += len; - return; - } - - room = len + 1; - } -} - -/* Silly helper using membuffer */ -char *vformat_string(const char *fmt, va_list args) -{ - struct membuffer mb = { 0 }; - put_vformat(&mb, fmt, args); - mb_cstring(&mb); - return detach_buffer(&mb); -} - -char *format_string(const char *fmt, ...) -{ - va_list args; - char *result; - - va_start(args, fmt); - result = vformat_string(fmt, args); - va_end(args); - return result; -} - -void put_format(struct membuffer *b, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - put_vformat(b, fmt, args); - va_end(args); -} - -void put_milli(struct membuffer *b, const char *pre, int value, const char *post) -{ - int i; - char buf[4]; - const char *sign = ""; - unsigned v; - - v = value; - if (value < 0) { - sign = "-"; - v = -value; - } - for (i = 2; i >= 0; i--) { - buf[i] = (v % 10) + '0'; - v /= 10; - } - buf[3] = 0; - if (buf[2] == '0') { - buf[2] = 0; - if (buf[1] == '0') - buf[1] = 0; - } - - put_format(b, "%s%s%u.%s%s", pre, sign, v, buf, post); -} - -void put_temperature(struct membuffer *b, temperature_t temp, const char *pre, const char *post) -{ - if (temp.mkelvin) - put_milli(b, pre, temp.mkelvin - ZERO_C_IN_MKELVIN, post); -} - -void put_depth(struct membuffer *b, depth_t depth, const char *pre, const char *post) -{ - if (depth.mm) - put_milli(b, pre, depth.mm, post); -} - -void put_duration(struct membuffer *b, duration_t duration, const char *pre, const char *post) -{ - if (duration.seconds) - put_format(b, "%s%u:%02u%s", pre, FRACTION(duration.seconds, 60), post); -} - -void put_pressure(struct membuffer *b, pressure_t pressure, const char *pre, const char *post) -{ - if (pressure.mbar) - put_milli(b, pre, pressure.mbar, post); -} - -void put_salinity(struct membuffer *b, int salinity, const char *pre, const char *post) -{ - if (salinity) - put_format(b, "%s%d%s", pre, salinity / 10, post); -} - -void put_degrees(struct membuffer *b, degrees_t value, const char *pre, const char *post) -{ - int udeg = value.udeg; - const char *sign = ""; - - if (udeg < 0) { - udeg = -udeg; - sign = "-"; - } - put_format(b, "%s%s%u.%06u%s", pre, sign, FRACTION(udeg, 1000000), post); -} - -void put_quoted(struct membuffer *b, const char *text, int is_attribute, int is_html) -{ - const char *p = text; - - for (;;) { - const char *escape; - - switch (*p++) { - default: - continue; - case 0: - escape = NULL; - break; - case 1 ... 8: - case 11: - case 12: - case 14 ... 31: - escape = "?"; - break; - case '<': - escape = "<"; - break; - case '>': - escape = ">"; - break; - case '&': - escape = "&"; - break; - case '\'': - if (!is_attribute) - continue; - escape = "'"; - break; - case '\"': - if (!is_attribute) - continue; - escape = """; - break; - case '\n': - if (!is_html) - continue; - else - escape = "<br>"; - } - put_bytes(b, text, (p - text - 1)); - if (!escape) - break; - put_string(b, escape); - text = p; - } -} - -char *add_to_string_va(const char *old, const char *fmt, va_list args) -{ - char *res; - struct membuffer o = { 0 }, n = { 0 }; - put_vformat(&n, fmt, args); - put_format(&o, "%s\n%s", old ?: "", mb_cstring(&n)); - res = strdup(mb_cstring(&o)); - free_buffer(&o); - free_buffer(&n); - free((void *)old); - return res; -} - -/* this is a convenience function that cleverly adds text to a string, using our membuffer - * infrastructure. - * WARNING - this will free(old), the intended pattern is - * string = add_to_string(string, fmt, ...) - */ -char *add_to_string(const char *old, const char *fmt, ...) -{ - char *res; - va_list args; - - va_start(args, fmt); - res = add_to_string_va(old, fmt, args); - va_end(args); - return res; -} diff --git a/subsurface-core/membuffer.h b/subsurface-core/membuffer.h deleted file mode 100644 index 434b34c71..000000000 --- a/subsurface-core/membuffer.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef MEMBUFFER_H -#define MEMBUFFER_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include <ctype.h> - -struct membuffer { - unsigned int len, alloc; - char *buffer; -}; - -#ifdef __GNUC__ -#define __printf(x, y) __attribute__((__format__(__printf__, x, y))) -#else -#define __printf(x, y) -#endif - -extern char *detach_buffer(struct membuffer *b); -extern void free_buffer(struct membuffer *); -extern void flush_buffer(struct membuffer *, FILE *); -extern void put_bytes(struct membuffer *, const char *, int); -extern void put_string(struct membuffer *, const char *); -extern void put_quoted(struct membuffer *, const char *, int, int); -extern void strip_mb(struct membuffer *); -extern const char *mb_cstring(struct membuffer *); -extern __printf(2, 0) void put_vformat(struct membuffer *, const char *, va_list); -extern __printf(2, 3) void put_format(struct membuffer *, const char *fmt, ...); -extern __printf(2, 0) char *add_to_string_va(const char *old, const char *fmt, va_list args); -extern __printf(2, 3) char *add_to_string(const char *old, const char *fmt, ...); - -/* Helpers that use membuffers internally */ -extern __printf(1, 0) char *vformat_string(const char *, va_list); -extern __printf(1, 2) char *format_string(const char *, ...); - - -/* Output one of our "milli" values with type and pre/post data */ -extern void put_milli(struct membuffer *, const char *, int, const char *); - -/* - * Helper functions for showing particular types. If the type - * is empty, nothing is done, and the function returns false. - * Otherwise, it returns true. - * - * The two "const char *" at the end are pre/post data. - * - * The reason for the pre/post data is so that you can easily - * prepend and append a string without having to test whether the - * type is empty. So - * - * put_temperature(b, temp, "Temp=", " C\n"); - * - * writes nothing to the buffer if there is no temperature data, - * but otherwise would a string that looks something like - * - * "Temp=28.1 C\n" - * - * to the memory buffer (typically the post/pre will be some XML - * pattern and unit string or whatever). - */ -extern void put_temperature(struct membuffer *, temperature_t, const char *, const char *); -extern void put_depth(struct membuffer *, depth_t, const char *, const char *); -extern void put_duration(struct membuffer *, duration_t, const char *, const char *); -extern void put_pressure(struct membuffer *, pressure_t, const char *, const char *); -extern void put_salinity(struct membuffer *, int, const char *, const char *); -extern void put_degrees(struct membuffer *b, degrees_t value, const char *, const char *); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/subsurface-core/metrics.cpp b/subsurface-core/metrics.cpp deleted file mode 100644 index 3c66528b8..000000000 --- a/subsurface-core/metrics.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * metrics.cpp - * - * methods to find/compute essential UI metrics - * (font properties, icon sizes, etc) - * - */ - -#include "metrics.h" - -static IconMetrics dfltIconMetrics; - -IconMetrics::IconMetrics() : - sz_small(-1), - sz_med(-1), - sz_big(-1), - sz_pic(-1), - spacing(-1), - dpr(1.0) -{ -} - -QFont defaultModelFont() -{ - QFont font; -// font.setPointSizeF(font.pointSizeF() * 0.8); - return font; -} - -QFontMetrics defaultModelFontMetrics() -{ - return QFontMetrics(defaultModelFont()); -} - -// return the default icon size, computed as the multiple of 16 closest to -// the given height -static int defaultIconSize(int height) -{ - int ret = (height + 8)/16; - ret *= 16; - if (ret < 16) - ret = 16; - return ret; -} - -const IconMetrics & defaultIconMetrics() -{ - if (dfltIconMetrics.sz_small == -1) { - int small = defaultIconSize(defaultModelFontMetrics().height()); - dfltIconMetrics.sz_small = small; - dfltIconMetrics.sz_med = small + small/2; - dfltIconMetrics.sz_big = 2*small; - - dfltIconMetrics.sz_pic = 8*small; - - dfltIconMetrics.spacing = small/8; - } - - return dfltIconMetrics; -} - -void updateDevicePixelRatio(double dpr) -{ - dfltIconMetrics.dpr = dpr; -} diff --git a/subsurface-core/metrics.h b/subsurface-core/metrics.h deleted file mode 100644 index ca281b3b1..000000000 --- a/subsurface-core/metrics.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * metrics.h - * - * header file for common function to find/compute essential UI metrics - * (font properties, icon sizes, etc) - * - */ -#ifndef METRICS_H -#define METRICS_H - -#include <QFont> -#include <QFontMetrics> -#include <QSize> - -QFont defaultModelFont(); -QFontMetrics defaultModelFontMetrics(); - -// Collection of icon/picture sizes and other metrics, resolution independent -struct IconMetrics { - // icon sizes - int sz_small; // ex 16px - int sz_med; // ex 24px - int sz_big; // ex 32px - // picture size - int sz_pic; // ex 128px - // icon spacing - int spacing; // ex 2px - // devicePixelRatio - double dpr; // 1.0 for traditional screens, HiDPI screens up to 3.0 - IconMetrics(); -}; - -const IconMetrics & defaultIconMetrics(); -void updateDevicePixelRatio(double dpr); - -#endif // METRICS_H diff --git a/subsurface-core/ostctools.c b/subsurface-core/ostctools.c deleted file mode 100644 index 9be591b0e..000000000 --- a/subsurface-core/ostctools.c +++ /dev/null @@ -1,193 +0,0 @@ -#include <stdlib.h> -#include <stdio.h> -#include <string.h> - -#include "dive.h" -#include "gettext.h" -#include "divelist.h" -#include "libdivecomputer.h" - -/* - * Returns a dc_descriptor_t structure based on dc model's number and family. - */ - -static dc_descriptor_t *ostc_get_data_descriptor(int data_model, dc_family_t data_fam) -{ - dc_descriptor_t *descriptor = NULL, *current = NULL; - ; - dc_iterator_t *iterator = NULL; - dc_status_t rc; - - rc = dc_descriptor_iterator(&iterator); - if (rc != DC_STATUS_SUCCESS) { - fprintf(stderr, "Error creating the device descriptor iterator.\n"); - return current; - } - while ((dc_iterator_next(iterator, &descriptor)) == DC_STATUS_SUCCESS) { - int desc_model = dc_descriptor_get_model(descriptor); - dc_family_t desc_fam = dc_descriptor_get_type(descriptor); - if (data_model == desc_model && data_fam == desc_fam) { - current = descriptor; - break; - } - dc_descriptor_free(descriptor); - } - dc_iterator_free(iterator); - return current; -} - -/* - * Fills a device_data_t structure with known dc data and a descriptor. - */ -static int ostc_prepare_data(int data_model, dc_family_t dc_fam, device_data_t *dev_data) -{ - dc_descriptor_t *data_descriptor; - - dev_data->device = NULL; - dev_data->context = NULL; - - data_descriptor = ostc_get_data_descriptor(data_model, dc_fam); - if (data_descriptor) { - dev_data->descriptor = data_descriptor; - dev_data->vendor = copy_string(data_descriptor->vendor); - dev_data->model = copy_string(data_descriptor->product); - } else { - return 0; - } - return 1; -} - -/* - * OSTCTools stores the raw dive data in heavily padded files, one dive - * each file. So it's not necessary to iterate once and again on a parsing - * function. Actually there's only one kind of archive for every DC model. - */ -void ostctools_import(const char *file, struct dive_table *divetable) -{ - FILE *archive; - device_data_t *devdata = calloc(1, sizeof(device_data_t)); - dc_family_t dc_fam; - unsigned char *buffer = calloc(65536, 1), *uc_tmp; - char *tmp; - struct dive *ostcdive = alloc_dive(); - dc_status_t rc = 0; - int model, ret, i = 0; - unsigned int serial; - struct extra_data *ptr; - - // Open the archive - if ((archive = subsurface_fopen(file, "rb")) == NULL) { - report_error(translate("gettextFromC", "Failed to read '%s'"), file); - free(ostcdive); - goto out; - } - - // Read dive number from the log - uc_tmp = calloc(2, 1); - fseek(archive, 258, 0); - fread(uc_tmp, 1, 2, archive); - ostcdive->number = uc_tmp[0] + (uc_tmp[1] << 8); - free(uc_tmp); - - // Read device's serial number - uc_tmp = calloc(2, 1); - fseek(archive, 265, 0); - fread(uc_tmp, 1, 2, archive); - serial = uc_tmp[0] + (uc_tmp[1] << 8); - free(uc_tmp); - - // Read dive's raw data, header + profile - fseek(archive, 456, 0); - while (!feof(archive)) { - fread(buffer + i, 1, 1, archive); - if (buffer[i] == 0xFD && buffer[i - 1] == 0xFD) - break; - i++; - } - - // Try to determine the dc family based on the header type - if (buffer[2] == 0x20 || buffer[2] == 0x21) { - dc_fam = DC_FAMILY_HW_OSTC; - } else { - switch (buffer[8]) { - case 0x22: - dc_fam = DC_FAMILY_HW_FROG; - break; - case 0x23: - dc_fam = DC_FAMILY_HW_OSTC3; - break; - default: - report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number); - free(ostcdive); - fclose(archive); - goto out; - } - } - - // Try to determine the model based on serial number - switch (dc_fam) { - case DC_FAMILY_HW_OSTC: - if (serial > 7000) - model = 3; //2C - else if (serial > 2048) - model = 2; //2N - else if (serial > 300) - model = 1; //MK2 - else - model = 0; //OSTC - break; - case DC_FAMILY_HW_FROG: - model = 0; - break; - default: - if (serial > 10000) - model = 0x12; //Sport - else - model = 0x0A; //OSTC3 - } - - // Prepare data to pass to libdivecomputer. - ret = ostc_prepare_data(model, dc_fam, devdata); - if (ret == 0) { - report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number); - free(ostcdive); - fclose(archive); - goto out; - } - tmp = calloc(strlen(devdata->vendor) + strlen(devdata->model) + 28, 1); - sprintf(tmp, "%s %s (Imported from OSTCTools)", devdata->vendor, devdata->model); - ostcdive->dc.model = copy_string(tmp); - free(tmp); - - // Parse the dive data - rc = libdc_buffer_parser(ostcdive, devdata, buffer, i + 1); - if (rc != DC_STATUS_SUCCESS) - report_error(translate("gettextFromC", "Error - %s - parsing dive %d"), errmsg(rc), ostcdive->number); - - // Serial number is not part of the header nor the profile, so libdc won't - // catch it. If Serial is part of the extra_data, and set to zero, remove - // it from the list and add again. - tmp = calloc(12, 1); - sprintf(tmp, "%d", serial); - ostcdive->dc.serial = copy_string(tmp); - free(tmp); - - if (ostcdive->dc.extra_data) { - ptr = ostcdive->dc.extra_data; - while (strcmp(ptr->key, "Serial")) - ptr = ptr->next; - if (!strcmp(ptr->value, "0")) { - add_extra_data(&ostcdive->dc, "Serial", ostcdive->dc.serial); - *ptr = *(ptr)->next; - } - } else { - add_extra_data(&ostcdive->dc, "Serial", ostcdive->dc.serial); - } - record_dive_to_table(ostcdive, divetable); - mark_divelist_changed(true); - sort_table(divetable); - fclose(archive); -out: - free(devdata); - free(buffer); -} diff --git a/subsurface-core/parse-xml.c b/subsurface-core/parse-xml.c deleted file mode 100644 index e8782251e..000000000 --- a/subsurface-core/parse-xml.c +++ /dev/null @@ -1,3751 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include <stdio.h> -#include <ctype.h> -#include <string.h> -#include <stdlib.h> -#include <errno.h> -#include <unistd.h> -#include <assert.h> -#define __USE_XOPEN -#include <time.h> -#include <libxml/parser.h> -#include <libxml/parserInternals.h> -#include <libxml/tree.h> -#include <libxslt/transform.h> -#include <libdivecomputer/parser.h> - -#include "gettext.h" - -#include "dive.h" -#include "divelist.h" -#include "device.h" -#include "membuffer.h" - -int verbose, quit, force_root; -int metric = 1; -int last_xml_version = -1; -int diveid = -1; - -static xmlDoc *test_xslt_transforms(xmlDoc *doc, const char **params); - -/* the dive table holds the overall dive list; target table points at - * the table we are currently filling */ -struct dive_table dive_table; -struct dive_table *target_table = NULL; - -/* Trim a character string by removing leading and trailing white space characters. - * Parameter: a pointer to a null-terminated character string (buffer); - * Return value: length of the trimmed string, excluding the terminal 0x0 byte - * The original pointer (buffer) remains valid after this function has been called - * and points to the trimmed string */ -int trimspace(char *buffer) { - int i, size, start, end; - size = strlen(buffer); - for(start = 0; isspace(buffer[start]); start++) - if (start >= size) return 0; // Find 1st character following leading whitespace - for(end = size - 1; isspace(buffer[end]); end--) // Find last character before trailing whitespace - if (end <= 0) return 0; - for(i = start; i <= end; i++) // Move the nonspace characters to the start of the string - buffer[i-start] = buffer[i]; - size = end - start + 1; - buffer[size] = 0x0; // then terminate the string - return size; // return string length -} - -/* - * Clear a dive_table - */ -void clear_table(struct dive_table *table) -{ - for (int i = 0; i < table->nr; i++) - free(table->dives[i]); - table->nr = 0; -} - -/* - * Add a dive into the dive_table array - */ -void record_dive_to_table(struct dive *dive, struct dive_table *table) -{ - assert(table != NULL); - struct dive **dives = grow_dive_table(table); - int nr = table->nr; - - dives[nr] = fixup_dive(dive); - table->nr = nr + 1; -} - -void record_dive(struct dive *dive) -{ - record_dive_to_table(dive, &dive_table); -} - -static void start_match(const char *type, const char *name, char *buffer) -{ - if (verbose > 2) - printf("Matching %s '%s' (%s)\n", - type, name, buffer); -} - -static void nonmatch(const char *type, const char *name, char *buffer) -{ - if (verbose > 1) - printf("Unable to match %s '%s' (%s)\n", - type, name, buffer); -} - -typedef void (*matchfn_t)(char *buffer, void *); - -static int match(const char *pattern, int plen, - const char *name, - matchfn_t fn, char *buf, void *data) -{ - switch (name[plen]) { - case '\0': - case '.': - break; - default: - return 0; - } - if (memcmp(pattern, name, plen)) - return 0; - fn(buf, data); - return 1; -} - - -struct units xml_parsing_units; -const struct units SI_units = SI_UNITS; -const struct units IMPERIAL_units = IMPERIAL_UNITS; - -/* - * Dive info as it is being built up.. - */ -#define MAX_EVENT_NAME 128 -static struct divecomputer *cur_dc; -static struct dive *cur_dive; -static struct dive_site *cur_dive_site; -degrees_t cur_latitude, cur_longitude; -static dive_trip_t *cur_trip = NULL; -static struct sample *cur_sample; -static struct picture *cur_picture; -static union { - struct event event; - char allocation[sizeof(struct event)+MAX_EVENT_NAME]; -} event_allocation = { .event.deleted = 1 }; -#define cur_event event_allocation.event -static struct { - struct { - const char *model; - uint32_t deviceid; - const char *nickname, *serial_nr, *firmware; - } dc; -} cur_settings; -static bool in_settings = false; -static bool in_userid = false; -static struct tm cur_tm; -static int cur_cylinder_index, cur_ws_index; -static int lastndl, laststoptime, laststopdepth, lastcns, lastpo2, lastindeco; -static int lastcylinderindex, lastsensor, next_o2_sensor; -static struct extra_data cur_extra_data; - -/* - * If we don't have an explicit dive computer, - * we use the implicit one that every dive has.. - */ -static struct divecomputer *get_dc(void) -{ - return cur_dc ?: &cur_dive->dc; -} - -static enum import_source { - UNKNOWN, - LIBDIVECOMPUTER, - DIVINGLOG, - UDDF, - SSRF_WS, -} import_source; - -static void divedate(const char *buffer, timestamp_t *when) -{ - int d, m, y; - int hh, mm, ss; - - hh = 0; - mm = 0; - ss = 0; - if (sscanf(buffer, "%d.%d.%d %d:%d:%d", &d, &m, &y, &hh, &mm, &ss) >= 3) { - /* This is ok, and we got at least the date */ - } else if (sscanf(buffer, "%d-%d-%d %d:%d:%d", &y, &m, &d, &hh, &mm, &ss) >= 3) { - /* This is also ok */ - } else { - fprintf(stderr, "Unable to parse date '%s'\n", buffer); - return; - } - cur_tm.tm_year = y; - cur_tm.tm_mon = m - 1; - cur_tm.tm_mday = d; - cur_tm.tm_hour = hh; - cur_tm.tm_min = mm; - cur_tm.tm_sec = ss; - - *when = utc_mktime(&cur_tm); -} - -static void divetime(const char *buffer, timestamp_t *when) -{ - int h, m, s = 0; - - if (sscanf(buffer, "%d:%d:%d", &h, &m, &s) >= 2) { - cur_tm.tm_hour = h; - cur_tm.tm_min = m; - cur_tm.tm_sec = s; - *when = utc_mktime(&cur_tm); - } -} - -/* Libdivecomputer: "2011-03-20 10:22:38" */ -static void divedatetime(char *buffer, timestamp_t *when) -{ - int y, m, d; - int hr, min, sec; - - if (sscanf(buffer, "%d-%d-%d %d:%d:%d", - &y, &m, &d, &hr, &min, &sec) == 6) { - cur_tm.tm_year = y; - cur_tm.tm_mon = m - 1; - cur_tm.tm_mday = d; - cur_tm.tm_hour = hr; - cur_tm.tm_min = min; - cur_tm.tm_sec = sec; - *when = utc_mktime(&cur_tm); - } -} - -enum ParseState { - FINDSTART, - FINDEND -}; -static void divetags(char *buffer, struct tag_entry **tags) -{ - int i = 0, start = 0, end = 0; - enum ParseState state = FINDEND; - int len = buffer ? strlen(buffer) : 0; - - while (i < len) { - if (buffer[i] == ',') { - if (state == FINDSTART) { - /* Detect empty tags */ - } else if (state == FINDEND) { - /* Found end of tag */ - if (i > 0 && buffer[i - 1] != '\\') { - buffer[i] = '\0'; - state = FINDSTART; - taglist_add_tag(tags, buffer + start); - } else { - state = FINDSTART; - } - } - } else if (buffer[i] == ' ') { - /* Handled */ - } else { - /* Found start of tag */ - if (state == FINDSTART) { - state = FINDEND; - start = i; - } else if (state == FINDEND) { - end = i; - } - } - i++; - } - if (state == FINDEND) { - if (end < start) - end = len - 1; - if (len > 0) { - buffer[end + 1] = '\0'; - taglist_add_tag(tags, buffer + start); - } - } -} - -enum number_type { - NEITHER, - FLOAT -}; - -static enum number_type parse_float(const char *buffer, double *res, const char **endp) -{ - double val; - static bool first_time = true; - - errno = 0; - val = ascii_strtod(buffer, endp); - if (errno || *endp == buffer) - return NEITHER; - if (**endp == ',') { - if (IS_FP_SAME(val, rint(val))) { - /* we really want to send an error if this is a Subsurface native file - * as this is likely indication of a bug - but right now we don't have - * that information available */ - if (first_time) { - fprintf(stderr, "Floating point value with decimal comma (%s)?\n", buffer); - first_time = false; - } - /* Try again in permissive mode*/ - val = strtod_flags(buffer, endp, 0); - } - } - - *res = val; - return FLOAT; -} - -union int_or_float { - double fp; -}; - -static enum number_type integer_or_float(char *buffer, union int_or_float *res) -{ - const char *end; - return parse_float(buffer, &res->fp, &end); -} - -static void pressure(char *buffer, pressure_t *pressure) -{ - double mbar = 0.0; - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - /* Just ignore zero values */ - if (!val.fp) - break; - switch (xml_parsing_units.pressure) { - case PASCAL: - mbar = val.fp / 100; - break; - case BAR: - /* Assume mbar, but if it's really small, it's bar */ - mbar = val.fp; - if (fabs(mbar) < 5000) - mbar = mbar * 1000; - break; - case PSI: - mbar = psi_to_mbar(val.fp); - break; - } - if (fabs(mbar) > 5 && fabs(mbar) < 5000000) { - pressure->mbar = rint(mbar); - break; - } - /* fallthrough */ - default: - printf("Strange pressure reading %s\n", buffer); - } -} - -static void cylinder_use(char *buffer, enum cylinderuse *cyl_use) -{ - if (trimspace(buffer)) - *cyl_use = cylinderuse_from_text(buffer); -} - -static void salinity(char *buffer, int *salinity) -{ - union int_or_float val; - switch (integer_or_float(buffer, &val)) { - case FLOAT: - *salinity = rint(val.fp * 10.0); - break; - default: - printf("Strange salinity reading %s\n", buffer); - } -} - -static void depth(char *buffer, depth_t *depth) -{ - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - switch (xml_parsing_units.length) { - case METERS: - depth->mm = rint(val.fp * 1000); - break; - case FEET: - depth->mm = feet_to_mm(val.fp); - break; - } - break; - default: - printf("Strange depth reading %s\n", buffer); - } -} - -static void extra_data_start(void) -{ - memset(&cur_extra_data, 0, sizeof(struct extra_data)); -} - -static void extra_data_end(void) -{ - // don't save partial structures - we must have both key and value - if (cur_extra_data.key && cur_extra_data.value) - add_extra_data(cur_dc, cur_extra_data.key, cur_extra_data.value); -} - -static void weight(char *buffer, weight_t *weight) -{ - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - switch (xml_parsing_units.weight) { - case KG: - weight->grams = rint(val.fp * 1000); - break; - case LBS: - weight->grams = lbs_to_grams(val.fp); - break; - } - break; - default: - printf("Strange weight reading %s\n", buffer); - } -} - -static void temperature(char *buffer, temperature_t *temperature) -{ - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - switch (xml_parsing_units.temperature) { - case KELVIN: - temperature->mkelvin = val.fp * 1000; - break; - case CELSIUS: - temperature->mkelvin = C_to_mkelvin(val.fp); - break; - case FAHRENHEIT: - temperature->mkelvin = F_to_mkelvin(val.fp); - break; - } - break; - default: - printf("Strange temperature reading %s\n", buffer); - } - /* temperatures outside -40C .. +70C should be ignored */ - if (temperature->mkelvin < ZERO_C_IN_MKELVIN - 40000 || - temperature->mkelvin > ZERO_C_IN_MKELVIN + 70000) - temperature->mkelvin = 0; -} - -static void sampletime(char *buffer, duration_t *time) -{ - int i; - int min, sec; - - i = sscanf(buffer, "%d:%d", &min, &sec); - switch (i) { - case 1: - sec = min; - min = 0; - /* fallthrough */ - case 2: - time->seconds = sec + min * 60; - break; - default: - printf("Strange sample time reading %s\n", buffer); - } -} - -static void offsettime(char *buffer, offset_t *time) -{ - duration_t uoffset; - int sign = 1; - if (*buffer == '-') { - sign = -1; - buffer++; - } - /* yes, this could indeed fail if we have an offset > 34yrs - * - too bad */ - sampletime(buffer, &uoffset); - time->seconds = sign * uoffset.seconds; -} - -static void duration(char *buffer, duration_t *time) -{ - /* DivingLog 5.08 (and maybe other versions) appear to sometimes - * store the dive time as 44.00 instead of 44:00; - * This attempts to parse this in a fairly robust way */ - if (!strchr(buffer, ':') && strchr(buffer, '.')) { - char *mybuffer = strdup(buffer); - char *dot = strchr(mybuffer, '.'); - *dot = ':'; - sampletime(mybuffer, time); - free(mybuffer); - } else { - sampletime(buffer, time); - } -} - -static void percent(char *buffer, fraction_t *fraction) -{ - double val; - const char *end; - - switch (parse_float(buffer, &val, &end)) { - case FLOAT: - /* Turn fractions into percent unless explicit.. */ - if (val <= 1.0) { - while (isspace(*end)) - end++; - if (*end != '%') - val *= 100; - } - - /* Then turn percent into our integer permille format */ - if (val >= 0 && val <= 100.0) { - fraction->permille = rint(val * 10); - break; - } - default: - printf(translate("gettextFromC", "Strange percentage reading %s\n"), buffer); - break; - } -} - -static void gasmix(char *buffer, fraction_t *fraction) -{ - /* libdivecomputer does negative percentages. */ - if (*buffer == '-') - return; - if (cur_cylinder_index < MAX_CYLINDERS) - percent(buffer, fraction); -} - -static void gasmix_nitrogen(char *buffer, struct gasmix *gasmix) -{ - (void) buffer; - (void) gasmix; - /* Ignore n2 percentages. There's no value in them. */ -} - -static void cylindersize(char *buffer, volume_t *volume) -{ - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - volume->mliter = rint(val.fp * 1000); - break; - - default: - printf("Strange volume reading %s\n", buffer); - break; - } -} - -static void utf8_string(char *buffer, void *_res) -{ - char **res = _res; - int size; - size = trimspace(buffer); - if(size) - *res = strdup(buffer); -} - -static void event_name(char *buffer, char *name) -{ - int size = trimspace(buffer); - if (size >= MAX_EVENT_NAME) - size = MAX_EVENT_NAME-1; - memcpy(name, buffer, size); - name[size] = 0; -} - -// We don't use gauge as a mode, and pscr doesn't exist as a libdc divemode -const char *libdc_divemode_text[] = { "oc", "cc", "pscr", "freedive", "gauge"}; - -/* Extract the dive computer type from the xml text buffer */ -static void get_dc_type(char *buffer, enum dive_comp_type *dct) -{ - if (trimspace(buffer)) { - for (enum dive_comp_type i = 0; i < NUM_DC_TYPE; i++) { - if (strcmp(buffer, divemode_text[i]) == 0) - *dct = i; - else if (strcmp(buffer, libdc_divemode_text[i]) == 0) - *dct = i; - } - } -} - -#define MATCH(pattern, fn, dest) ({ \ - /* Silly type compatibility test */ \ - if (0) (fn)("test", dest); \ - match(pattern, strlen(pattern), name, (matchfn_t) (fn), buf, dest); }) - -static void get_index(char *buffer, int *i) -{ - *i = atoi(buffer); -} - -static void get_uint8(char *buffer, uint8_t *i) -{ - *i = atoi(buffer); -} - -static void get_bearing(char *buffer, bearing_t *bearing) -{ - bearing->degrees = atoi(buffer); -} - -static void get_rating(char *buffer, int *i) -{ - int j = atoi(buffer); - if (j >= 0 && j <= 5) { - *i = j; - } -} - -static void double_to_o2pressure(char *buffer, o2pressure_t *i) -{ - i->mbar = rint(ascii_strtod(buffer, NULL) * 1000.0); -} - -static void hex_value(char *buffer, uint32_t *i) -{ - *i = strtoul(buffer, NULL, 16); -} - -static void get_tripflag(char *buffer, tripflag_t *tf) -{ - *tf = strcmp(buffer, "NOTRIP") ? TF_NONE : NO_TRIP; -} - -/* - * Divinglog is crazy. The temperatures are in celsius. EXCEPT - * for the sample temperatures, that are in Fahrenheit. - * WTF? - * - * Oh, and I think Diving Log *internally* probably kept them - * in celsius, because I'm seeing entries like - * - * <Temp>32.0</Temp> - * - * in there. Which is freezing, aka 0 degC. I bet the "0" is - * what Diving Log uses for "no temperature". - * - * So throw away crap like that. - * - * It gets worse. Sometimes the sample temperatures are in - * Celsius, which apparently happens if you are in a SI - * locale. So we now do: - * - * - temperatures < 32.0 == Celsius - * - temperature == 32.0 -> garbage, it's a missing temperature (zero converted from C to F) - * - temperatures > 32.0 == Fahrenheit - */ -static void fahrenheit(char *buffer, temperature_t *temperature) -{ - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - if (IS_FP_SAME(val.fp, 32.0)) - break; - if (val.fp < 32.0) - temperature->mkelvin = C_to_mkelvin(val.fp); - else - temperature->mkelvin = F_to_mkelvin(val.fp); - break; - default: - fprintf(stderr, "Crazy Diving Log temperature reading %s\n", buffer); - } -} - -/* - * Did I mention how bat-shit crazy divinglog is? The sample - * pressures are in PSI. But the tank working pressure is in - * bar. WTF^2? - * - * Crazy stuff like this is why subsurface has everything in - * these inconvenient typed structures, and you have to say - * "pressure->mbar" to get the actual value. Exactly so that - * you can never have unit confusion. - * - * It gets worse: sometimes apparently the pressures are in - * bar, sometimes in psi. Dirk suspects that this may be a - * DivingLog Uemis importer bug, and that they are always - * supposed to be in bar, but that the importer got the - * sample importing wrong. - * - * Sadly, there's no way to really tell. So I think we just - * have to have some arbitrary cut-off point where we assume - * that smaller values mean bar.. Not good. - */ -static void psi_or_bar(char *buffer, pressure_t *pressure) -{ - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - if (val.fp > 400) - pressure->mbar = psi_to_mbar(val.fp); - else - pressure->mbar = rint(val.fp * 1000); - break; - default: - fprintf(stderr, "Crazy Diving Log PSI reading %s\n", buffer); - } -} - -static int divinglog_fill_sample(struct sample *sample, const char *name, char *buf) -{ - return MATCH("time.p", sampletime, &sample->time) || - MATCH("depth.p", depth, &sample->depth) || - MATCH("temp.p", fahrenheit, &sample->temperature) || - MATCH("press1.p", psi_or_bar, &sample->cylinderpressure) || - 0; -} - -static void uddf_gasswitch(char *buffer, struct sample *sample) -{ - int idx = atoi(buffer); - int seconds = sample->time.seconds; - struct dive *dive = cur_dive; - struct divecomputer *dc = get_dc(); - - add_gas_switch_event(dive, dc, seconds, idx); -} - -static int uddf_fill_sample(struct sample *sample, const char *name, char *buf) -{ - return MATCH("divetime", sampletime, &sample->time) || - MATCH("depth", depth, &sample->depth) || - MATCH("temperature", temperature, &sample->temperature) || - MATCH("tankpressure", pressure, &sample->cylinderpressure) || - MATCH("ref.switchmix", uddf_gasswitch, sample) || - 0; -} - -static void eventtime(char *buffer, duration_t *duration) -{ - sampletime(buffer, duration); - if (cur_sample) - duration->seconds += cur_sample->time.seconds; -} - -static void try_to_match_autogroup(const char *name, char *buf) -{ - int autogroupvalue; - - start_match("autogroup", name, buf); - if (MATCH("state.autogroup", get_index, &autogroupvalue)) { - set_autogroup(autogroupvalue); - return; - } - nonmatch("autogroup", name, buf); -} - -void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int seconds, int idx) -{ - /* sanity check so we don't crash */ - if (idx < 0 || idx >= MAX_CYLINDERS) - return; - /* The gas switch event format is insane for historical reasons */ - struct gasmix *mix = &dive->cylinder[idx].gasmix; - int o2 = get_o2(mix); - int he = get_he(mix); - struct event *ev; - int value; - - o2 = (o2 + 5) / 10; - he = (he + 5) / 10; - value = o2 + (he << 16); - - ev = add_event(dc, seconds, he ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE, 0, value, "gaschange"); - if (ev) { - ev->gas.index = idx; - ev->gas.mix = *mix; - } -} - -static void get_cylinderindex(char *buffer, uint8_t *i) -{ - *i = atoi(buffer); - if (lastcylinderindex != *i) { - add_gas_switch_event(cur_dive, get_dc(), cur_sample->time.seconds, *i); - lastcylinderindex = *i; - } -} - -static void get_sensor(char *buffer, uint8_t *i) -{ - *i = atoi(buffer); - lastsensor = *i; -} - -static void parse_libdc_deco(char *buffer, struct sample *s) -{ - if (strcmp(buffer, "deco") == 0) { - s->in_deco = true; - } else if (strcmp(buffer, "ndl") == 0) { - s->in_deco = false; - // The time wasn't stoptime, it was ndl - s->ndl = s->stoptime; - s->stoptime.seconds = 0; - } -} - -static void try_to_fill_dc_settings(const char *name, char *buf) -{ - start_match("divecomputerid", name, buf); - if (MATCH("model.divecomputerid", utf8_string, &cur_settings.dc.model)) - return; - if (MATCH("deviceid.divecomputerid", hex_value, &cur_settings.dc.deviceid)) - return; - if (MATCH("nickname.divecomputerid", utf8_string, &cur_settings.dc.nickname)) - return; - if (MATCH("serial.divecomputerid", utf8_string, &cur_settings.dc.serial_nr)) - return; - if (MATCH("firmware.divecomputerid", utf8_string, &cur_settings.dc.firmware)) - return; - - nonmatch("divecomputerid", name, buf); -} - -static void try_to_fill_event(const char *name, char *buf) -{ - start_match("event", name, buf); - if (MATCH("event", event_name, cur_event.name)) - return; - if (MATCH("name", event_name, cur_event.name)) - return; - if (MATCH("time", eventtime, &cur_event.time)) - return; - if (MATCH("type", get_index, &cur_event.type)) - return; - if (MATCH("flags", get_index, &cur_event.flags)) - return; - if (MATCH("value", get_index, &cur_event.value)) - return; - if (MATCH("cylinder", get_index, &cur_event.gas.index)) { - /* We add one to indicate that we got an actual cylinder index value */ - cur_event.gas.index++; - return; - } - if (MATCH("o2", percent, &cur_event.gas.mix.o2)) - return; - if (MATCH("he", percent, &cur_event.gas.mix.he)) - return; - nonmatch("event", name, buf); -} - -static int match_dc_data_fields(struct divecomputer *dc, const char *name, char *buf) -{ - if (MATCH("maxdepth", depth, &dc->maxdepth)) - return 1; - if (MATCH("meandepth", depth, &dc->meandepth)) - return 1; - if (MATCH("max.depth", depth, &dc->maxdepth)) - return 1; - if (MATCH("mean.depth", depth, &dc->meandepth)) - return 1; - if (MATCH("duration", duration, &dc->duration)) - return 1; - if (MATCH("divetime", duration, &dc->duration)) - return 1; - if (MATCH("divetimesec", duration, &dc->duration)) - return 1; - if (MATCH("surfacetime", duration, &dc->surfacetime)) - return 1; - if (MATCH("airtemp", temperature, &dc->airtemp)) - return 1; - if (MATCH("watertemp", temperature, &dc->watertemp)) - return 1; - if (MATCH("air.temperature", temperature, &dc->airtemp)) - return 1; - if (MATCH("water.temperature", temperature, &dc->watertemp)) - return 1; - if (MATCH("pressure.surface", pressure, &dc->surface_pressure)) - return 1; - if (MATCH("salinity.water", salinity, &dc->salinity)) - return 1; - if (MATCH("key.extradata", utf8_string, &cur_extra_data.key)) - return 1; - if (MATCH("value.extradata", utf8_string, &cur_extra_data.value)) - return 1; - if (MATCH("divemode", get_dc_type, &dc->divemode)) - return 1; - if (MATCH("salinity", salinity, &dc->salinity)) - return 1; - if (MATCH("atmospheric", pressure, &dc->surface_pressure)) - return 1; - return 0; -} - -/* We're in the top-level dive xml. Try to convert whatever value to a dive value */ -static void try_to_fill_dc(struct divecomputer *dc, const char *name, char *buf) -{ - start_match("divecomputer", name, buf); - - if (MATCH("date", divedate, &dc->when)) - return; - if (MATCH("time", divetime, &dc->when)) - return; - if (MATCH("model", utf8_string, &dc->model)) - return; - if (MATCH("deviceid", hex_value, &dc->deviceid)) - return; - if (MATCH("diveid", hex_value, &dc->diveid)) - return; - if (MATCH("dctype", get_dc_type, &dc->divemode)) - return; - if (MATCH("no_o2sensors", get_sensor, &dc->no_o2sensors)) - return; - if (match_dc_data_fields(dc, name, buf)) - return; - - nonmatch("divecomputer", name, buf); -} - -/* We're in samples - try to convert the random xml value to something useful */ -static void try_to_fill_sample(struct sample *sample, const char *name, char *buf) -{ - int in_deco; - - start_match("sample", name, buf); - if (MATCH("pressure.sample", pressure, &sample->cylinderpressure)) - return; - if (MATCH("cylpress.sample", pressure, &sample->cylinderpressure)) - return; - if (MATCH("pdiluent.sample", pressure, &sample->cylinderpressure)) - return; - if (MATCH("o2pressure.sample", pressure, &sample->o2cylinderpressure)) - return; - if (MATCH("cylinderindex.sample", get_cylinderindex, &sample->sensor)) - return; - if (MATCH("sensor.sample", get_sensor, &sample->sensor)) - return; - if (MATCH("depth.sample", depth, &sample->depth)) - return; - if (MATCH("temp.sample", temperature, &sample->temperature)) - return; - if (MATCH("temperature.sample", temperature, &sample->temperature)) - return; - if (MATCH("sampletime.sample", sampletime, &sample->time)) - return; - if (MATCH("time.sample", sampletime, &sample->time)) - return; - if (MATCH("ndl.sample", sampletime, &sample->ndl)) - return; - if (MATCH("tts.sample", sampletime, &sample->tts)) - return; - if (MATCH("in_deco.sample", get_index, &in_deco)) { - sample->in_deco = (in_deco == 1); - return; - } - if (MATCH("stoptime.sample", sampletime, &sample->stoptime)) - return; - if (MATCH("stopdepth.sample", depth, &sample->stopdepth)) - return; - if (MATCH("cns.sample", get_uint8, &sample->cns)) - return; - if (MATCH("rbt.sample", sampletime, &sample->rbt)) - return; - if (MATCH("sensor1.sample", double_to_o2pressure, &sample->o2sensor[0])) // CCR O2 sensor data - return; - if (MATCH("sensor2.sample", double_to_o2pressure, &sample->o2sensor[1])) - return; - if (MATCH("sensor3.sample", double_to_o2pressure, &sample->o2sensor[2])) // up to 3 CCR sensors - return; - if (MATCH("po2.sample", double_to_o2pressure, &sample->setpoint)) - return; - if (MATCH("heartbeat", get_uint8, &sample->heartbeat)) - return; - if (MATCH("bearing", get_bearing, &sample->bearing)) - return; - if (MATCH("setpoint.sample", double_to_o2pressure, &sample->setpoint)) - return; - if (MATCH("ppo2.sample", double_to_o2pressure, &sample->o2sensor[next_o2_sensor])) { - next_o2_sensor++; - return; - } - if (MATCH("deco.sample", parse_libdc_deco, sample)) - return; - if (MATCH("time.deco", sampletime, &sample->stoptime)) - return; - if (MATCH("depth.deco", depth, &sample->stopdepth)) - return; - - switch (import_source) { - case DIVINGLOG: - if (divinglog_fill_sample(sample, name, buf)) - return; - break; - - case UDDF: - if (uddf_fill_sample(sample, name, buf)) - return; - break; - - default: - break; - } - - nonmatch("sample", name, buf); -} - -void try_to_fill_userid(const char *name, char *buf) -{ - (void) name; - if (prefs.save_userid_local) - set_userid(buf); -} - -static const char *country, *city; - -static void divinglog_place(char *place, uint32_t *uuid) -{ - char buffer[1024]; - - snprintf(buffer, sizeof(buffer), - "%s%s%s%s%s", - place, - city ? ", " : "", - city ? city : "", - country ? ", " : "", - country ? country : ""); - *uuid = get_dive_site_uuid_by_name(buffer, NULL); - if (*uuid == 0) - *uuid = create_dive_site(buffer, cur_dive->when); - - city = NULL; - country = NULL; -} - -static int divinglog_dive_match(struct dive *dive, const char *name, char *buf) -{ - return MATCH("divedate", divedate, &dive->when) || - MATCH("entrytime", divetime, &dive->when) || - MATCH("divetime", duration, &dive->dc.duration) || - MATCH("depth", depth, &dive->dc.maxdepth) || - MATCH("depthavg", depth, &dive->dc.meandepth) || - MATCH("tanktype", utf8_string, &dive->cylinder[0].type.description) || - MATCH("tanksize", cylindersize, &dive->cylinder[0].type.size) || - MATCH("presw", pressure, &dive->cylinder[0].type.workingpressure) || - MATCH("press", pressure, &dive->cylinder[0].start) || - MATCH("prese", pressure, &dive->cylinder[0].end) || - MATCH("comments", utf8_string, &dive->notes) || - MATCH("names.buddy", utf8_string, &dive->buddy) || - MATCH("name.country", utf8_string, &country) || - MATCH("name.city", utf8_string, &city) || - MATCH("name.place", divinglog_place, &dive->dive_site_uuid) || - 0; -} - -/* - * Uddf specifies ISO 8601 time format. - * - * There are many variations on that. This handles the useful cases. - */ -static void uddf_datetime(char *buffer, timestamp_t *when) -{ - char c; - int y, m, d, hh, mm, ss; - struct tm tm = { 0 }; - int i; - - i = sscanf(buffer, "%d-%d-%d%c%d:%d:%d", &y, &m, &d, &c, &hh, &mm, &ss); - if (i == 7) - goto success; - ss = 0; - if (i == 6) - goto success; - - i = sscanf(buffer, "%04d%02d%02d%c%02d%02d%02d", &y, &m, &d, &c, &hh, &mm, &ss); - if (i == 7) - goto success; - ss = 0; - if (i == 6) - goto success; -bad_date: - printf("Bad date time %s\n", buffer); - return; - -success: - if (c != 'T' && c != ' ') - goto bad_date; - tm.tm_year = y; - tm.tm_mon = m - 1; - tm.tm_mday = d; - tm.tm_hour = hh; - tm.tm_min = mm; - tm.tm_sec = ss; - *when = utc_mktime(&tm); -} - -#define uddf_datedata(name, offset) \ - static void uddf_##name(char *buffer, timestamp_t *when) \ - { \ - cur_tm.tm_##name = atoi(buffer) + offset; \ - *when = utc_mktime(&cur_tm); \ - } - -uddf_datedata(year, 0) -uddf_datedata(mon, -1) -uddf_datedata(mday, 0) -uddf_datedata(hour, 0) -uddf_datedata(min, 0) - -static int uddf_dive_match(struct dive *dive, const char *name, char *buf) -{ - return MATCH("datetime", uddf_datetime, &dive->when) || - MATCH("diveduration", duration, &dive->dc.duration) || - MATCH("greatestdepth", depth, &dive->dc.maxdepth) || - MATCH("year.date", uddf_year, &dive->when) || - MATCH("month.date", uddf_mon, &dive->when) || - MATCH("day.date", uddf_mday, &dive->when) || - MATCH("hour.time", uddf_hour, &dive->when) || - MATCH("minute.time", uddf_min, &dive->when) || - 0; -} - -/* - * This parses "floating point" into micro-degrees. - * We don't do exponentials etc, if somebody does - * GPS locations in that format, they are insane. - */ -degrees_t parse_degrees(char *buf, char **end) -{ - int sign = 1, decimals = 6, value = 0; - degrees_t ret; - - while (isspace(*buf)) - buf++; - switch (*buf) { - case '-': - sign = -1; - /* fallthrough */ - case '+': - buf++; - } - while (isdigit(*buf)) { - value = 10 * value + *buf - '0'; - buf++; - } - - /* Get the first six decimals if they exist */ - if (*buf == '.') - buf++; - do { - value *= 10; - if (isdigit(*buf)) { - value += *buf - '0'; - buf++; - } - } while (--decimals); - - /* Rounding */ - switch (*buf) { - case '5' ... '9': - value++; - } - while (isdigit(*buf)) - buf++; - - *end = buf; - ret.udeg = value * sign; - return ret; -} - -static void gps_lat(char *buffer, struct dive *dive) -{ - char *end; - degrees_t latitude = parse_degrees(buffer, &end); - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds) { - dive->dive_site_uuid = create_dive_site_with_gps(NULL, latitude, (degrees_t){0}, dive->when); - } else { - if (ds->latitude.udeg && ds->latitude.udeg != latitude.udeg) - fprintf(stderr, "Oops, changing the latitude of existing dive site id %8x name %s; not good\n", ds->uuid, ds->name ?: "(unknown)"); - ds->latitude = latitude; - } -} - -static void gps_long(char *buffer, struct dive *dive) -{ - char *end; - degrees_t longitude = parse_degrees(buffer, &end); - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds) { - dive->dive_site_uuid = create_dive_site_with_gps(NULL, (degrees_t){0}, longitude, dive->when); - } else { - if (ds->longitude.udeg && ds->longitude.udeg != longitude.udeg) - fprintf(stderr, "Oops, changing the longitude of existing dive site id %8x name %s; not good\n", ds->uuid, ds->name ?: "(unknown)"); - ds->longitude = longitude; - } - -} - -static void gps_location(char *buffer, struct dive_site *ds) -{ - char *end; - - ds->latitude = parse_degrees(buffer, &end); - ds->longitude = parse_degrees(end, &end); -} - -/* this is in qthelper.cpp, so including the .h file is a pain */ -extern const char *printGPSCoords(int lat, int lon); - -static void gps_in_dive(char *buffer, struct dive *dive) -{ - char *end; - struct dive_site *ds = NULL; - degrees_t latitude = parse_degrees(buffer, &end); - degrees_t longitude = parse_degrees(end, &end); - uint32_t uuid = dive->dive_site_uuid; - if (uuid == 0) { - // check if we have a dive site within 20 meters of that gps fix - uuid = get_dive_site_uuid_by_gps_proximity(latitude, longitude, 20, &ds); - - if (ds) { - // found a site nearby; in case it turns out this one had a different name let's - // remember the original coordinates so we can create the correct dive site later - cur_latitude = latitude; - cur_longitude = longitude; - dive->dive_site_uuid = uuid; - } else { - dive->dive_site_uuid = create_dive_site_with_gps("", latitude, longitude, dive->when); - ds = get_dive_site_by_uuid(dive->dive_site_uuid); - } - } else { - ds = get_dive_site_by_uuid(uuid); - if (dive_site_has_gps_location(ds) && - (latitude.udeg != 0 || longitude.udeg != 0) && - (ds->latitude.udeg != latitude.udeg || ds->longitude.udeg != longitude.udeg)) { - // Houston, we have a problem - fprintf(stderr, "dive site uuid in dive, but gps location (%10.6f/%10.6f) different from dive location (%10.6f/%10.6f)\n", - ds->latitude.udeg / 1000000.0, ds->longitude.udeg / 1000000.0, - latitude.udeg / 1000000.0, longitude.udeg / 1000000.0); - const char *coords = printGPSCoords(latitude.udeg, longitude.udeg); - ds->notes = add_to_string(ds->notes, translate("gettextFromC", "multiple GPS locations for this dive site; also %s\n"), coords); - free((void *)coords); - } else { - ds->latitude = latitude; - ds->longitude = longitude; - } - } -} - -static void add_dive_site(char *ds_name, struct dive *dive) -{ - static int suffix = 1; - char *buffer = ds_name; - char *to_free = NULL; - int size = trimspace(buffer); - if(size) { - uint32_t uuid = dive->dive_site_uuid; - struct dive_site *ds = get_dive_site_by_uuid(uuid); - if (uuid && !ds) { - // that's strange - we have a uuid but it doesn't exist - let's just ignore it - fprintf(stderr, "dive contains a non-existing dive site uuid %x\n", dive->dive_site_uuid); - uuid = 0; - } - if (!uuid) { - // if the dive doesn't have a uuid, check if there's already a dive site by this name - uuid = get_dive_site_uuid_by_name(buffer, &ds); - if (uuid && import_source == SSRF_WS) { - // when downloading GPS fixes from the Subsurface webservice we will often - // get a lot of dives with identical names (the autogenerated fixes). - // So in this case modify the name to make it unique - int name_size = strlen(buffer) + 10; // 8 digits - enough for 100 million sites - to_free = buffer = malloc(name_size); - do { - suffix++; - snprintf(buffer, name_size, "%s %8d", ds_name, suffix); - } while (get_dive_site_uuid_by_name(buffer, NULL) != 0); - ds = NULL; - } - } - if (ds) { - // we have a uuid, let's hope there isn't a different name - if (same_string(ds->name, "")) { - ds->name = copy_string(buffer); - } else if (!same_string(ds->name, buffer)) { - // if it's not the same name, it's not the same dive site - // but wait, we could have gotten this one based on GPS coords and could - // have had two different names for the same site... so let's search the other - // way around - uint32_t exact_match_uuid = get_dive_site_uuid_by_gps_and_name(buffer, ds->latitude, ds->longitude); - if (exact_match_uuid) { - dive->dive_site_uuid = exact_match_uuid; - } else { - dive->dive_site_uuid = create_dive_site(buffer, dive->when); - struct dive_site *newds = get_dive_site_by_uuid(dive->dive_site_uuid); - if (cur_latitude.udeg || cur_longitude.udeg) { - // we started this uuid with GPS data, so lets use those - newds->latitude = cur_latitude; - newds->longitude = cur_longitude; - } else { - newds->latitude = ds->latitude; - newds->longitude = ds->longitude; - } - newds->notes = add_to_string(newds->notes, translate("gettextFromC", "additional name for site: %s\n"), ds->name); - } - } else { - // add the existing dive site to the current dive - dive->dive_site_uuid = uuid; - } - } else { - dive->dive_site_uuid = create_dive_site(buffer, dive->when); - } - } - free(to_free); -} - -static void gps_picture_location(char *buffer, struct picture *pic) -{ - char *end; - - pic->latitude = parse_degrees(buffer, &end); - pic->longitude = parse_degrees(end, &end); -} - -/* We're in the top-level dive xml. Try to convert whatever value to a dive value */ -static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) -{ - start_match("dive", name, buf); - - switch (import_source) { - case DIVINGLOG: - if (divinglog_dive_match(dive, name, buf)) - return; - break; - - case UDDF: - if (uddf_dive_match(dive, name, buf)) - return; - break; - - default: - break; - } - if (MATCH("divesiteid", hex_value, &dive->dive_site_uuid)) - return; - if (MATCH("number", get_index, &dive->number)) - return; - if (MATCH("tags", divetags, &dive->tag_list)) - return; - if (MATCH("tripflag", get_tripflag, &dive->tripflag)) - return; - if (MATCH("date", divedate, &dive->when)) - return; - if (MATCH("time", divetime, &dive->when)) - return; - if (MATCH("datetime", divedatetime, &dive->when)) - return; - /* - * Legacy format note: per-dive depths and duration get saved - * in the first dive computer entry - */ - if (match_dc_data_fields(&dive->dc, name, buf)) - return; - - if (MATCH("filename.picture", utf8_string, &cur_picture->filename)) - return; - if (MATCH("offset.picture", offsettime, &cur_picture->offset)) - 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)) - return; - if (MATCH("gps", gps_in_dive, dive)) - return; - if (MATCH("Place", gps_in_dive, dive)) - return; - if (MATCH("latitude", gps_lat, dive)) - return; - if (MATCH("sitelat", gps_lat, dive)) - return; - if (MATCH("lat", gps_lat, dive)) - return; - if (MATCH("longitude", gps_long, dive)) - return; - if (MATCH("sitelon", gps_long, dive)) - return; - if (MATCH("lon", gps_long, dive)) - return; - if (MATCH("location", add_dive_site, dive)) - return; - if (MATCH("name.dive", add_dive_site, dive)) - return; - if (MATCH("suit", utf8_string, &dive->suit)) - return; - if (MATCH("divesuit", utf8_string, &dive->suit)) - return; - if (MATCH("notes", utf8_string, &dive->notes)) - return; - if (MATCH("divemaster", utf8_string, &dive->divemaster)) - return; - if (MATCH("buddy", utf8_string, &dive->buddy)) - return; - if (MATCH("rating.dive", get_rating, &dive->rating)) - return; - if (MATCH("visibility.dive", get_rating, &dive->visibility)) - return; - if (cur_ws_index < MAX_WEIGHTSYSTEMS) { - if (MATCH("description.weightsystem", utf8_string, &dive->weightsystem[cur_ws_index].description)) - return; - if (MATCH("weight.weightsystem", weight, &dive->weightsystem[cur_ws_index].weight)) - return; - if (MATCH("weight", weight, &dive->weightsystem[cur_ws_index].weight)) - return; - } - if (cur_cylinder_index < MAX_CYLINDERS) { - if (MATCH("size.cylinder", cylindersize, &dive->cylinder[cur_cylinder_index].type.size)) - return; - if (MATCH("workpressure.cylinder", pressure, &dive->cylinder[cur_cylinder_index].type.workingpressure)) - return; - if (MATCH("description.cylinder", utf8_string, &dive->cylinder[cur_cylinder_index].type.description)) - return; - if (MATCH("start.cylinder", pressure, &dive->cylinder[cur_cylinder_index].start)) - return; - if (MATCH("end.cylinder", pressure, &dive->cylinder[cur_cylinder_index].end)) - return; - if (MATCH("use.cylinder", cylinder_use, &dive->cylinder[cur_cylinder_index].cylinder_use)) - return; - if (MATCH("o2", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.o2)) - return; - if (MATCH("o2percent", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.o2)) - return; - if (MATCH("n2", gasmix_nitrogen, &dive->cylinder[cur_cylinder_index].gasmix)) - return; - if (MATCH("he", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.he)) - return; - } - if (MATCH("air.divetemperature", temperature, &dive->airtemp)) - return; - if (MATCH("water.divetemperature", temperature, &dive->watertemp)) - return; - - nonmatch("dive", name, buf); -} - -/* We're in the top-level trip xml. Try to convert whatever value to a trip value */ -static void try_to_fill_trip(dive_trip_t **dive_trip_p, const char *name, char *buf) -{ - start_match("trip", name, buf); - - dive_trip_t *dive_trip = *dive_trip_p; - - if (MATCH("date", divedate, &dive_trip->when)) - return; - if (MATCH("time", divetime, &dive_trip->when)) - return; - if (MATCH("location", utf8_string, &dive_trip->location)) - return; - if (MATCH("notes", utf8_string, &dive_trip->notes)) - return; - - nonmatch("trip", name, buf); -} - -/* We're processing a divesite entry - try to fill the components */ -static void try_to_fill_dive_site(struct dive_site **ds_p, const char *name, char *buf) -{ - start_match("divesite", name, buf); - - struct dive_site *ds = *ds_p; - if (ds->taxonomy.category == NULL) - ds->taxonomy.category = alloc_taxonomy(); - - if (MATCH("uuid", hex_value, &ds->uuid)) - return; - if (MATCH("name", utf8_string, &ds->name)) - return; - if (MATCH("description", utf8_string, &ds->description)) - return; - if (MATCH("notes", utf8_string, &ds->notes)) - return; - if (MATCH("gps", gps_location, ds)) - return; - if (MATCH("cat.geo", get_index, (int *)&ds->taxonomy.category[ds->taxonomy.nr].category)) - return; - if (MATCH("origin.geo", get_index, (int *)&ds->taxonomy.category[ds->taxonomy.nr].origin)) - return; - if (MATCH("value.geo", utf8_string, &ds->taxonomy.category[ds->taxonomy.nr].value)) { - if (ds->taxonomy.nr < TC_NR_CATEGORIES) - ds->taxonomy.nr++; - return; - } - - nonmatch("divesite", name, buf); -} - -/* - * While in some formats file boundaries are dive boundaries, in many - * others (as for example in our native format) there are - * multiple dives per file, so there can be other events too that - * trigger a "new dive" marker and you may get some nesting due - * to that. Just ignore nesting levels. - * On the flipside it is possible that we start an XML file that ends - * up having no dives in it at all - don't create a bogus empty dive - * for those. It's not entirely clear what is the minimum set of data - * to make a dive valid, but if it has no location, no date and no - * samples I'm pretty sure it's useless. - */ -static bool is_dive(void) -{ - return (cur_dive && - (cur_dive->dive_site_uuid || cur_dive->when || cur_dive->dc.samples)); -} - -static void reset_dc_info(struct divecomputer *dc) -{ - /* WARN: reset dc info does't touch the dc? */ - (void) dc; - lastcns = lastpo2 = lastndl = laststoptime = laststopdepth = lastindeco = 0; - lastsensor = lastcylinderindex = 0; -} - -static void reset_dc_settings(void) -{ - free((void *)cur_settings.dc.model); - free((void *)cur_settings.dc.nickname); - free((void *)cur_settings.dc.serial_nr); - free((void *)cur_settings.dc.firmware); - cur_settings.dc.model = NULL; - cur_settings.dc.nickname = NULL; - cur_settings.dc.serial_nr = NULL; - cur_settings.dc.firmware = NULL; - cur_settings.dc.deviceid = 0; -} - -static void settings_start(void) -{ - in_settings = true; -} - -static void settings_end(void) -{ - in_settings = false; -} - -static void dc_settings_start(void) -{ - reset_dc_settings(); -} - -static void dc_settings_end(void) -{ - create_device_node(cur_settings.dc.model, cur_settings.dc.deviceid, cur_settings.dc.serial_nr, - cur_settings.dc.firmware, cur_settings.dc.nickname); - reset_dc_settings(); -} - -static void dive_site_start(void) -{ - if (cur_dive_site) - return; - cur_dive_site = calloc(1, sizeof(struct dive_site)); -} - -static void dive_site_end(void) -{ - if (!cur_dive_site) - return; - if (cur_dive_site->uuid) { - // we intentionally call this with '0' to ensure we get - // a new structure and then copy things into that new - // structure a few lines below (which sets the correct - // uuid) - struct dive_site *ds = alloc_or_get_dive_site(0); - if (cur_dive_site->taxonomy.nr == 0) { - free(cur_dive_site->taxonomy.category); - cur_dive_site->taxonomy.category = NULL; - } - copy_dive_site(cur_dive_site, ds); - - if (verbose > 3) - printf("completed dive site uuid %x8 name {%s}\n", ds->uuid, ds->name); - } - free_taxonomy(&cur_dive_site->taxonomy); - free(cur_dive_site); - cur_dive_site = NULL; -} - -// now we need to add the code to parse the parts of the divesite enry - -static void dive_start(void) -{ - if (cur_dive) - return; - cur_dive = alloc_dive(); - reset_dc_info(&cur_dive->dc); - memset(&cur_tm, 0, sizeof(cur_tm)); - if (cur_trip) { - add_dive_to_trip(cur_dive, cur_trip); - cur_dive->tripflag = IN_TRIP; - } -} - -static void dive_end(void) -{ - if (!cur_dive) - return; - if (!is_dive()) - free(cur_dive); - else - record_dive_to_table(cur_dive, target_table); - cur_dive = NULL; - cur_dc = NULL; - cur_latitude.udeg = 0; - cur_longitude.udeg = 0; - cur_cylinder_index = 0; - cur_ws_index = 0; -} - -static void trip_start(void) -{ - if (cur_trip) - return; - dive_end(); - cur_trip = calloc(1, sizeof(dive_trip_t)); - memset(&cur_tm, 0, sizeof(cur_tm)); -} - -static void trip_end(void) -{ - if (!cur_trip) - return; - insert_trip(&cur_trip); - cur_trip = NULL; -} - -static void event_start(void) -{ - memset(&cur_event, 0, sizeof(cur_event)); - cur_event.deleted = 0; /* Active */ -} - -static void event_end(void) -{ - struct divecomputer *dc = get_dc(); - if (strcmp(cur_event.name, "surface") != 0) { /* 123 is a magic event that we used for a while to encode images in dives */ - if (cur_event.type == 123) { - struct picture *pic = alloc_picture(); - pic->filename = strdup(cur_event.name); - /* theoretically this could fail - but we didn't support multi year offsets */ - pic->offset.seconds = cur_event.time.seconds; - dive_add_picture(cur_dive, pic); - } else { - struct event *ev; - /* At some point gas change events did not have any type. Thus we need to add - * one on import, if we encounter the type one missing. - */ - if (cur_event.type == 0 && strcmp(cur_event.name, "gaschange") == 0) - cur_event.type = cur_event.value >> 16 > 0 ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE; - ev = add_event(dc, cur_event.time.seconds, - cur_event.type, cur_event.flags, - cur_event.value, cur_event.name); - - /* - * Older logs might mark the dive to be CCR by having an "SP change" event at time 0:00. Better - * to mark them being CCR on import so no need for special treatments elsewhere on the code. - */ - if (ev && cur_event.time.seconds == 0 && cur_event.type == SAMPLE_EVENT_PO2 && dc->divemode==OC) { - dc->divemode = CCR; - } - - if (ev && event_is_gaschange(ev)) { - /* See try_to_fill_event() on why the filled-in index is one too big */ - ev->gas.index = cur_event.gas.index-1; - if (cur_event.gas.mix.o2.permille || cur_event.gas.mix.he.permille) - ev->gas.mix = cur_event.gas.mix; - } - } - } - cur_event.deleted = 1; /* No longer active */ -} - -static void picture_start(void) -{ - cur_picture = alloc_picture(); -} - -static void picture_end(void) -{ - dive_add_picture(cur_dive, cur_picture); - cur_picture = NULL; -} - -static void cylinder_start(void) -{ -} - -static void cylinder_end(void) -{ - cur_cylinder_index++; -} - -static void ws_start(void) -{ -} - -static void ws_end(void) -{ - cur_ws_index++; -} - -static void sample_start(void) -{ - cur_sample = prepare_sample(get_dc()); - cur_sample->ndl.seconds = lastndl; - cur_sample->in_deco = lastindeco; - cur_sample->stoptime.seconds = laststoptime; - cur_sample->stopdepth.mm = laststopdepth; - cur_sample->cns = lastcns; - cur_sample->setpoint.mbar = lastpo2; - cur_sample->sensor = lastsensor; - next_o2_sensor = 0; -} - -static void sample_end(void) -{ - if (!cur_dive) - return; - - finish_sample(get_dc()); - lastndl = cur_sample->ndl.seconds; - lastindeco = cur_sample->in_deco; - laststoptime = cur_sample->stoptime.seconds; - laststopdepth = cur_sample->stopdepth.mm; - lastcns = cur_sample->cns; - lastpo2 = cur_sample->setpoint.mbar; - cur_sample = NULL; -} - -static void divecomputer_start(void) -{ - struct divecomputer *dc; - - /* Start from the previous dive computer */ - dc = &cur_dive->dc; - while (dc->next) - dc = dc->next; - - /* Did we already fill that in? */ - if (dc->samples || dc->model || dc->when) { - struct divecomputer *newdc = calloc(1, sizeof(*newdc)); - if (newdc) { - dc->next = newdc; - dc = newdc; - } - } - - /* .. this is the one we'll use */ - cur_dc = dc; - reset_dc_info(dc); -} - -static void divecomputer_end(void) -{ - if (!cur_dc->when) - cur_dc->when = cur_dive->when; - cur_dc = NULL; -} - -static void userid_start(void) -{ - in_userid = true; - set_save_userid_local(true); //if the xml contains userid, keep saving it. -} - -static void userid_stop(void) -{ - in_userid = false; -} - -static bool entry(const char *name, char *buf) -{ - if (!strncmp(name, "version.program", sizeof("version.program") - 1) || - !strncmp(name, "version.divelog", sizeof("version.divelog") - 1)) { - last_xml_version = atoi(buf); - report_datafile_version(last_xml_version); - } - if (in_userid) { - try_to_fill_userid(name, buf); - return true; - } - if (in_settings) { - try_to_fill_dc_settings(name, buf); - try_to_match_autogroup(name, buf); - return true; - } - if (cur_dive_site) { - try_to_fill_dive_site(&cur_dive_site, name, buf); - return true; - } - if (!cur_event.deleted) { - try_to_fill_event(name, buf); - return true; - } - if (cur_sample) { - try_to_fill_sample(cur_sample, name, buf); - return true; - } - if (cur_dc) { - try_to_fill_dc(cur_dc, name, buf); - return true; - } - if (cur_dive) { - try_to_fill_dive(cur_dive, name, buf); - return true; - } - if (cur_trip) { - try_to_fill_trip(&cur_trip, name, buf); - return true; - } - return true; -} - -static const char *nodename(xmlNode *node, char *buf, int len) -{ - int levels = 2; - char *p = buf; - - if (!node || (node->type != XML_CDATA_SECTION_NODE && !node->name)) { - return "root"; - } - - if (node->type == XML_CDATA_SECTION_NODE || (node->parent && !strcmp((const char *)node->name, "text"))) - node = node->parent; - - /* Make sure it's always NUL-terminated */ - p[--len] = 0; - - for (;;) { - const char *name = (const char *)node->name; - char c; - while ((c = *name++) != 0) { - /* Cheaper 'tolower()' for ASCII */ - c = (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c; - *p++ = c; - if (!--len) - return buf; - } - *p = 0; - node = node->parent; - if (!node || !node->name) - return buf; - *p++ = '.'; - if (!--len) - return buf; - if (!--levels) - return buf; - } -} - -#define MAXNAME 32 - -static bool visit_one_node(xmlNode *node) -{ - xmlChar *content; - static char buffer[MAXNAME]; - const char *name; - - content = node->content; - if (!content || xmlIsBlankNode(node)) - return true; - - name = nodename(node, buffer, sizeof(buffer)); - - return entry(name, (char *)content); -} - -static bool traverse(xmlNode *root); - -static bool traverse_properties(xmlNode *node) -{ - xmlAttr *p; - bool ret = true; - - for (p = node->properties; p; p = p->next) - if ((ret = traverse(p->children)) == false) - break; - return ret; -} - -static bool visit(xmlNode *n) -{ - return visit_one_node(n) && traverse_properties(n) && traverse(n->children); -} - -static void DivingLog_importer(void) -{ - import_source = DIVINGLOG; - - /* - * Diving Log units are really strange. - * - * Temperatures are in C, except in samples, - * when they are in Fahrenheit. Depths are in - * meters, an dpressure is in PSI in the samples, - * but in bar when it comes to working pressure. - * - * Crazy f*%^ morons. - */ - xml_parsing_units = SI_units; -} - -static void uddf_importer(void) -{ - import_source = UDDF; - xml_parsing_units = SI_units; - xml_parsing_units.pressure = PASCAL; - xml_parsing_units.temperature = KELVIN; -} - -static void subsurface_webservice(void) -{ - import_source = SSRF_WS; -} - -/* - * I'm sure this could be done as some fancy DTD rules. - * It's just not worth the headache. - */ -static struct nesting { - const char *name; - void (*start)(void), (*end)(void); -} nesting[] = { - { "divecomputerid", dc_settings_start, dc_settings_end }, - { "settings", settings_start, settings_end }, - { "site", dive_site_start, dive_site_end }, - { "dive", dive_start, dive_end }, - { "Dive", dive_start, dive_end }, - { "trip", trip_start, trip_end }, - { "sample", sample_start, sample_end }, - { "waypoint", sample_start, sample_end }, - { "SAMPLE", sample_start, sample_end }, - { "reading", sample_start, sample_end }, - { "event", event_start, event_end }, - { "mix", cylinder_start, cylinder_end }, - { "gasmix", cylinder_start, cylinder_end }, - { "cylinder", cylinder_start, cylinder_end }, - { "weightsystem", ws_start, ws_end }, - { "divecomputer", divecomputer_start, divecomputer_end }, - { "P", sample_start, sample_end }, - { "userid", userid_start, userid_stop}, - { "picture", picture_start, picture_end }, - { "extradata", extra_data_start, extra_data_end }, - - /* Import type recognition */ - { "Divinglog", DivingLog_importer }, - { "uddf", uddf_importer }, - { "output", subsurface_webservice }, - { NULL, } - }; - -static bool traverse(xmlNode *root) -{ - xmlNode *n; - bool ret = true; - - for (n = root; n; n = n->next) { - struct nesting *rule = nesting; - - if (!n->name) { - if ((ret = visit(n)) == false) - break; - continue; - } - - do { - if (!strcmp(rule->name, (const char *)n->name)) - break; - rule++; - } while (rule->name); - - if (rule->start) - rule->start(); - if ((ret = visit(n)) == false) - break; - if (rule->end) - rule->end(); - } - return ret; -} - -/* Per-file reset */ -static void reset_all(void) -{ - /* - * We reset the units for each file. You'd think it was - * a per-dive property, but I'm not going to trust people - * to do per-dive setup. If the xml does have per-dive - * data within one file, we might have to reset it per - * dive for that format. - */ - xml_parsing_units = SI_units; - import_source = UNKNOWN; -} - -/* divelog.de sends us xml files that claim to be iso-8859-1 - * but once we decode the HTML encoded characters they turn - * into UTF-8 instead. So skip the incorrect encoding - * declaration and decode the HTML encoded characters */ -const char *preprocess_divelog_de(const char *buffer) -{ - char *ret = strstr(buffer, "<DIVELOGSDATA>"); - - if (ret) { - xmlParserCtxtPtr ctx; - char buf[] = ""; - size_t i; - - for (i = 0; i < strlen(ret); ++i) - if (!isascii(ret[i])) - return buffer; - - ctx = xmlCreateMemoryParserCtxt(buf, sizeof(buf)); - ret = (char *)xmlStringLenDecodeEntities(ctx, (xmlChar *)ret, strlen(ret), XML_SUBSTITUTE_REF, 0, 0, 0); - - return ret; - } - return buffer; -} - -int parse_xml_buffer(const char *url, const char *buffer, int size, - struct dive_table *table, const char **params) -{ - (void) size; - xmlDoc *doc; - const char *res = preprocess_divelog_de(buffer); - int ret = 0; - - target_table = table; - doc = xmlReadMemory(res, strlen(res), url, NULL, 0); - if (res != buffer) - free((char *)res); - - if (!doc) - return report_error(translate("gettextFromC", "Failed to parse '%s'"), url); - - set_save_userid_local(false); - reset_all(); - dive_start(); - doc = test_xslt_transforms(doc, params); - if (!traverse(xmlDocGetRootElement(doc))) { - // we decided to give up on parsing... why? - ret = -1; - } - dive_end(); - xmlFreeDoc(doc); - return ret; -} - -void parse_mkvi_buffer(struct membuffer *txt, struct membuffer *csv, const char *starttime) -{ - (void) csv; - (void) txt; - dive_start(); - divedate(starttime, &cur_dive->when); - dive_end(); -} - -extern int dm4_events(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - event_start(); - if (data[1]) - cur_event.time.seconds = atoi(data[1]); - - if (data[2]) { - switch (atoi(data[2])) { - case 1: - /* 1 Mandatory Safety Stop */ - strcpy(cur_event.name, "safety stop (mandatory)"); - break; - case 3: - /* 3 Deco */ - /* What is Subsurface's term for going to - * deco? */ - strcpy(cur_event.name, "deco"); - break; - case 4: - /* 4 Ascent warning */ - strcpy(cur_event.name, "ascent"); - break; - case 5: - /* 5 Ceiling broken */ - strcpy(cur_event.name, "violation"); - break; - case 6: - /* 6 Mandatory safety stop ceiling error */ - strcpy(cur_event.name, "violation"); - break; - case 7: - /* 7 Below deco floor */ - strcpy(cur_event.name, "below floor"); - break; - case 8: - /* 8 Dive time alarm */ - strcpy(cur_event.name, "divetime"); - break; - case 9: - /* 9 Depth alarm */ - strcpy(cur_event.name, "maxdepth"); - break; - case 10: - /* 10 OLF 80% */ - case 11: - /* 11 OLF 100% */ - strcpy(cur_event.name, "OLF"); - break; - case 12: - /* 12 High pOâ‚‚ */ - strcpy(cur_event.name, "PO2"); - break; - case 13: - /* 13 Air time */ - strcpy(cur_event.name, "airtime"); - break; - case 17: - /* 17 Ascent warning */ - strcpy(cur_event.name, "ascent"); - break; - case 18: - /* 18 Ceiling error */ - strcpy(cur_event.name, "ceiling"); - break; - case 19: - /* 19 Surfaced */ - strcpy(cur_event.name, "surface"); - break; - case 20: - /* 20 Deco */ - strcpy(cur_event.name, "deco"); - break; - case 22: - case 32: - /* 22 Mandatory safety stop violation */ - /* 32 Deep stop violation */ - strcpy(cur_event.name, "violation"); - break; - case 30: - /* Tissue level warning */ - strcpy(cur_event.name, "tissue warning"); - break; - case 37: - /* Tank pressure alarm */ - strcpy(cur_event.name, "tank pressure"); - break; - case 257: - /* 257 Dive active */ - /* This seems to be given after surface when - * descending again. */ - strcpy(cur_event.name, "surface"); - break; - case 258: - /* 258 Bookmark */ - if (data[3]) { - strcpy(cur_event.name, "heading"); - cur_event.value = atoi(data[3]); - } else { - strcpy(cur_event.name, "bookmark"); - } - break; - case 259: - /* Deep stop */ - strcpy(cur_event.name, "Deep stop"); - break; - case 260: - /* Deep stop */ - strcpy(cur_event.name, "Deep stop cleared"); - break; - case 266: - /* Mandatory safety stop activated */ - strcpy(cur_event.name, "safety stop (mandatory)"); - break; - case 267: - /* Mandatory safety stop deactivated */ - /* DM5 shows this only on event list, not on the - * profile so skipping as well for now */ - break; - default: - strcpy(cur_event.name, "unknown"); - cur_event.value = atoi(data[2]); - break; - } - } - event_end(); - - return 0; -} - -extern int dm5_cylinders(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - cylinder_start(); - if (data[7] && atoi(data[7]) > 0 && atoi(data[7]) < 350000) - cur_dive->cylinder[cur_cylinder_index].start.mbar = atoi(data[7]); - if (data[8] && atoi(data[8]) > 0 && atoi(data[8]) < 350000) - cur_dive->cylinder[cur_cylinder_index].end.mbar = (atoi(data[8])); - if (data[6]) { - /* DM5 shows tank size of 12 liters when the actual - * value is 0 (and using metric units). So we just use - * the same 12 liters when size is not available */ - if (atof(data[6]) == 0.0 && cur_dive->cylinder[cur_cylinder_index].start.mbar) - cur_dive->cylinder[cur_cylinder_index].type.size.mliter = 12000; - else - cur_dive->cylinder[cur_cylinder_index].type.size.mliter = (atof(data[6])) * 1000; - } - if (data[2]) - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atoi(data[2]) * 10; - if (data[3]) - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atoi(data[3]) * 10; - cylinder_end(); - return 0; -} - -extern int dm5_gaschange(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - event_start(); - if (data[0]) - cur_event.time.seconds = atoi(data[0]); - if (data[1]) { - strcpy(cur_event.name, "gaschange"); - cur_event.value = atof(data[1]); - } - event_end(); - - return 0; -} - -extern int dm4_tags(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - if (data[0]) - taglist_add_tag(&cur_dive->tag_list, data[0]); - - return 0; -} - -extern int dm4_dive(void *param, int columns, char **data, char **column) -{ - (void) columns; - (void) column; - unsigned int i; - int interval, retval = 0; - sqlite3 *handle = (sqlite3 *)param; - float *profileBlob; - unsigned char *tempBlob; - int *pressureBlob; - char *err = NULL; - char get_events_template[] = "select * from Mark where DiveId = %d"; - char get_tags_template[] = "select Text from DiveTag where DiveId = %d"; - char get_events[64]; - - dive_start(); - cur_dive->number = atoi(data[0]); - - cur_dive->when = (time_t)(atol(data[1])); - if (data[2]) - utf8_string(data[2], &cur_dive->notes); - - /* - * DM4 stores Duration and DiveTime. It looks like DiveTime is - * 10 to 60 seconds shorter than Duration. However, I have no - * idea what is the difference and which one should be used. - * Duration = data[3] - * DiveTime = data[15] - */ - if (data[3]) - cur_dive->duration.seconds = atoi(data[3]); - if (data[15]) - cur_dive->dc.duration.seconds = atoi(data[15]); - - /* - * TODO: the deviceid hash should be calculated here. - */ - settings_start(); - dc_settings_start(); - if (data[4]) - utf8_string(data[4], &cur_settings.dc.serial_nr); - if (data[5]) - utf8_string(data[5], &cur_settings.dc.model); - - cur_settings.dc.deviceid = 0xffffffff; - dc_settings_end(); - settings_end(); - - if (data[6]) - cur_dive->dc.maxdepth.mm = atof(data[6]) * 1000; - if (data[8]) - cur_dive->dc.airtemp.mkelvin = C_to_mkelvin(atoi(data[8])); - if (data[9]) - cur_dive->dc.watertemp.mkelvin = C_to_mkelvin(atoi(data[9])); - - /* - * TODO: handle multiple cylinders - */ - cylinder_start(); - if (data[22] && atoi(data[22]) > 0) - cur_dive->cylinder[cur_cylinder_index].start.mbar = atoi(data[22]); - else if (data[10] && atoi(data[10]) > 0) - cur_dive->cylinder[cur_cylinder_index].start.mbar = atoi(data[10]); - if (data[23] && atoi(data[23]) > 0) - cur_dive->cylinder[cur_cylinder_index].end.mbar = (atoi(data[23])); - if (data[11] && atoi(data[11]) > 0) - cur_dive->cylinder[cur_cylinder_index].end.mbar = (atoi(data[11])); - if (data[12]) - cur_dive->cylinder[cur_cylinder_index].type.size.mliter = (atof(data[12])) * 1000; - if (data[13]) - cur_dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = (atoi(data[13])); - if (data[20]) - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atoi(data[20]) * 10; - if (data[21]) - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atoi(data[21]) * 10; - cylinder_end(); - - if (data[14]) - cur_dive->dc.surface_pressure.mbar = (atoi(data[14]) * 1000); - - interval = data[16] ? atoi(data[16]) : 0; - profileBlob = (float *)data[17]; - tempBlob = (unsigned char *)data[18]; - pressureBlob = (int *)data[19]; - for (i = 0; interval && i * interval < cur_dive->duration.seconds; i++) { - sample_start(); - cur_sample->time.seconds = i * interval; - if (profileBlob) - cur_sample->depth.mm = profileBlob[i] * 1000; - else - cur_sample->depth.mm = cur_dive->dc.maxdepth.mm; - - if (data[18] && data[18][0]) - cur_sample->temperature.mkelvin = C_to_mkelvin(tempBlob[i]); - if (data[19] && data[19][0]) - cur_sample->cylinderpressure.mbar = pressureBlob[i]; - sample_end(); - } - - snprintf(get_events, sizeof(get_events) - 1, get_events_template, cur_dive->number); - retval = sqlite3_exec(handle, get_events, &dm4_events, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query dm4_events failed.\n"); - return 1; - } - - snprintf(get_events, sizeof(get_events) - 1, get_tags_template, cur_dive->number); - retval = sqlite3_exec(handle, get_events, &dm4_tags, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query dm4_tags failed.\n"); - return 1; - } - - dive_end(); - - /* - for (i=0; i<columns;++i) { - fprintf(stderr, "%s\t", column[i]); - } - fprintf(stderr, "\n"); - for (i=0; i<columns;++i) { - fprintf(stderr, "%s\t", data[i]); - } - fprintf(stderr, "\n"); - //exit(0); - */ - return SQLITE_OK; -} - -extern int dm5_dive(void *param, int columns, char **data, char **column) -{ - (void) columns; - (void) column; - unsigned int i; - int interval, retval = 0, block_size; - sqlite3 *handle = (sqlite3 *)param; - unsigned const char *sampleBlob; - char *err = NULL; - char get_events_template[] = "select * from Mark where DiveId = %d"; - char get_tags_template[] = "select Text from DiveTag where DiveId = %d"; - char get_cylinders_template[] = "select * from DiveMixture where DiveId = %d"; - char get_gaschange_template[] = "select GasChangeTime,Oxygen,Helium from DiveGasChange join DiveMixture on DiveGasChange.DiveMixtureId=DiveMixture.DiveMixtureId where DiveId = %d"; - char get_events[512]; - - dive_start(); - cur_dive->number = atoi(data[0]); - - cur_dive->when = (time_t)(atol(data[1])); - if (data[2]) - utf8_string(data[2], &cur_dive->notes); - - if (data[3]) - cur_dive->duration.seconds = atoi(data[3]); - if (data[15]) - cur_dive->dc.duration.seconds = atoi(data[15]); - - /* - * TODO: the deviceid hash should be calculated here. - */ - settings_start(); - dc_settings_start(); - if (data[4]) { - utf8_string(data[4], &cur_settings.dc.serial_nr); - cur_settings.dc.deviceid = atoi(data[4]); - } - if (data[5]) - utf8_string(data[5], &cur_settings.dc.model); - - dc_settings_end(); - settings_end(); - - if (data[6]) - cur_dive->dc.maxdepth.mm = atof(data[6]) * 1000; - if (data[8]) - cur_dive->dc.airtemp.mkelvin = C_to_mkelvin(atoi(data[8])); - if (data[9]) - cur_dive->dc.watertemp.mkelvin = C_to_mkelvin(atoi(data[9])); - - if (data[4]) { - cur_dive->dc.deviceid = atoi(data[4]); - } - if (data[5]) - utf8_string(data[5], &cur_dive->dc.model); - - snprintf(get_events, sizeof(get_events) - 1, get_cylinders_template, cur_dive->number); - retval = sqlite3_exec(handle, get_events, &dm5_cylinders, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query dm5_cylinders failed.\n"); - return 1; - } - - if (data[14]) - cur_dive->dc.surface_pressure.mbar = (atoi(data[14]) / 100); - - interval = data[16] ? atoi(data[16]) : 0; - sampleBlob = (unsigned const char *)data[24]; - - if (sampleBlob) { - switch (sampleBlob[0]) { - case 2: - block_size = 19; - break; - case 3: - block_size = 23; - break; - default: - block_size = 16; - break; - } - } - - for (i = 0; interval && sampleBlob && i * interval < cur_dive->duration.seconds; i++) { - float *depth = (float *)&sampleBlob[i * block_size + 3]; - int32_t temp = (sampleBlob[i * block_size + 10] << 8) + sampleBlob[i * block_size + 11]; - int32_t pressure = (sampleBlob[i * block_size + 9] << 16) + (sampleBlob[i * block_size + 8] << 8) + sampleBlob[i * block_size + 7]; - - sample_start(); - cur_sample->time.seconds = i * interval; - cur_sample->depth.mm = depth[0] * 1000; - /* - * Limit temperatures and cylinder pressures to somewhat - * sensible values - */ - if (temp >= -10 && temp < 50) - cur_sample->temperature.mkelvin = C_to_mkelvin(temp); - if (pressure >= 0 && pressure < 350000) - cur_sample->cylinderpressure.mbar = pressure; - sample_end(); - } - - /* - * Log was converted from DM4, thus we need to parse the profile - * from DM4 format - */ - - if (i == 0) { - float *profileBlob; - unsigned char *tempBlob; - int *pressureBlob; - - profileBlob = (float *)data[17]; - tempBlob = (unsigned char *)data[18]; - pressureBlob = (int *)data[19]; - for (i = 0; interval && i * interval < cur_dive->duration.seconds; i++) { - sample_start(); - cur_sample->time.seconds = i * interval; - if (profileBlob) - cur_sample->depth.mm = profileBlob[i] * 1000; - else - cur_sample->depth.mm = cur_dive->dc.maxdepth.mm; - - if (data[18] && data[18][0]) - cur_sample->temperature.mkelvin = C_to_mkelvin(tempBlob[i]); - if (data[19] && data[19][0]) - cur_sample->cylinderpressure.mbar = pressureBlob[i]; - sample_end(); - } - } - - snprintf(get_events, sizeof(get_events) - 1, get_gaschange_template, cur_dive->number); - retval = sqlite3_exec(handle, get_events, &dm5_gaschange, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query dm5_gaschange failed.\n"); - return 1; - } - - snprintf(get_events, sizeof(get_events) - 1, get_events_template, cur_dive->number); - retval = sqlite3_exec(handle, get_events, &dm4_events, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query dm4_events failed.\n"); - return 1; - } - - snprintf(get_events, sizeof(get_events) - 1, get_tags_template, cur_dive->number); - retval = sqlite3_exec(handle, get_events, &dm4_tags, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query dm4_tags failed.\n"); - return 1; - } - - dive_end(); - - return SQLITE_OK; -} - - -int parse_dm4_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, - struct dive_table *table) -{ - (void) buffer; - (void) size; - - int retval; - char *err = NULL; - target_table = table; - - /* StartTime is converted from Suunto's nano seconds to standard - * time. We also need epoch, not seconds since year 1. */ - char get_dives[] = "select D.DiveId,StartTime/10000000-62135596800,Note,Duration,SourceSerialNumber,Source,MaxDepth,SampleInterval,StartTemperature,BottomTemperature,D.StartPressure,D.EndPressure,Size,CylinderWorkPressure,SurfacePressure,DiveTime,SampleInterval,ProfileBlob,TemperatureBlob,PressureBlob,Oxygen,Helium,MIX.StartPressure,MIX.EndPressure FROM Dive AS D JOIN DiveMixture AS MIX ON D.DiveId=MIX.DiveId"; - - retval = sqlite3_exec(handle, get_dives, &dm4_dive, handle, &err); - - if (retval != SQLITE_OK) { - fprintf(stderr, "Database query failed '%s'.\n", url); - return 1; - } - - return 0; -} - -int parse_dm5_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, - struct dive_table *table) -{ - (void) buffer; - (void) size; - - int retval; - char *err = NULL; - target_table = table; - - /* StartTime is converted from Suunto's nano seconds to standard - * time. We also need epoch, not seconds since year 1. */ - char get_dives[] = "select DiveId,StartTime/10000000-62135596800,Note,Duration,coalesce(SourceSerialNumber,SerialNumber),Source,MaxDepth,SampleInterval,StartTemperature,BottomTemperature,StartPressure,EndPressure,'','',SurfacePressure,DiveTime,SampleInterval,ProfileBlob,TemperatureBlob,PressureBlob,'','','','',SampleBlob FROM Dive where Deleted is null"; - - retval = sqlite3_exec(handle, get_dives, &dm5_dive, handle, &err); - - if (retval != SQLITE_OK) { - fprintf(stderr, "Database query failed '%s'.\n", url); - return 1; - } - - return 0; -} - -extern int shearwater_cylinders(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - cylinder_start(); - if (data[0]) - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atof(data[0]) * 1000; - if (data[1]) - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atof(data[1]) * 1000; - cylinder_end(); - - return 0; -} - -extern int shearwater_changes(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - event_start(); - if (data[0]) - cur_event.time.seconds = atoi(data[0]); - if (data[1]) { - strcpy(cur_event.name, "gaschange"); - cur_event.value = atof(data[1]) * 100; - } - event_end(); - - return 0; -} - - -extern int cobalt_profile_sample(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - sample_start(); - if (data[0]) - cur_sample->time.seconds = atoi(data[0]); - if (data[1]) - cur_sample->depth.mm = atoi(data[1]); - if (data[2]) - cur_sample->temperature.mkelvin = metric ? C_to_mkelvin(atof(data[2])) : F_to_mkelvin(atof(data[2])); - sample_end(); - - return 0; -} - - -extern int shearwater_profile_sample(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - sample_start(); - if (data[0]) - cur_sample->time.seconds = atoi(data[0]); - if (data[1]) - cur_sample->depth.mm = metric ? atof(data[1]) * 1000 : feet_to_mm(atof(data[1])); - if (data[2]) - cur_sample->temperature.mkelvin = metric ? C_to_mkelvin(atof(data[2])) : F_to_mkelvin(atof(data[2])); - if (data[3]) { - cur_sample->setpoint.mbar = atof(data[3]) * 1000; - cur_dive->dc.divemode = CCR; - } - if (data[4]) - cur_sample->ndl.seconds = atoi(data[4]) * 60; - if (data[5]) - cur_sample->cns = atoi(data[5]); - if (data[6]) - cur_sample->stopdepth.mm = metric ? atoi(data[6]) * 1000 : feet_to_mm(atoi(data[6])); - - /* We don't actually have data[3], but it should appear in the - * SQL query at some point. - if (data[3]) - cur_sample->cylinderpressure.mbar = metric ? atoi(data[3]) * 1000 : psi_to_mbar(atoi(data[3])); - */ - sample_end(); - - return 0; -} - -extern int shearwater_dive(void *param, int columns, char **data, char **column) -{ - (void) columns; - (void) column; - - int retval = 0; - sqlite3 *handle = (sqlite3 *)param; - char *err = NULL; - char get_profile_template[] = "select currentTime,currentDepth,waterTemp,averagePPO2,currentNdl,CNSPercent,decoCeiling from dive_log_records where diveLogId = %d"; - char get_cylinder_template[] = "select fractionO2,fractionHe from dive_log_records where diveLogId = %d group by fractionO2,fractionHe"; - char get_changes_template[] = "select a.currentTime,a.fractionO2,a.fractionHe from dive_log_records as a,dive_log_records as b where a.diveLogId = %d and b.diveLogId = %d and (a.id - 1) = b.id and (a.fractionO2 != b.fractionO2 or a.fractionHe != b.fractionHe) union select min(currentTime),fractionO2,fractionHe from dive_log_records"; - char get_buffer[1024]; - - dive_start(); - cur_dive->number = atoi(data[0]); - - cur_dive->when = (time_t)(atol(data[1])); - - if (data[2]) - add_dive_site(data[2], cur_dive); - if (data[3]) - utf8_string(data[3], &cur_dive->buddy); - if (data[4]) - utf8_string(data[4], &cur_dive->notes); - - metric = atoi(data[5]) == 1 ? 0 : 1; - - /* TODO: verify that metric calculation is correct */ - if (data[6]) - cur_dive->dc.maxdepth.mm = metric ? atof(data[6]) * 1000 : feet_to_mm(atof(data[6])); - - if (data[7]) - cur_dive->dc.duration.seconds = atoi(data[7]) * 60; - - if (data[8]) - cur_dive->dc.surface_pressure.mbar = atoi(data[8]); - /* - * TODO: the deviceid hash should be calculated here. - */ - settings_start(); - dc_settings_start(); - if (data[9]) - utf8_string(data[9], &cur_settings.dc.serial_nr); - if (data[10]) - utf8_string(data[10], &cur_settings.dc.model); - - cur_settings.dc.deviceid = 0xffffffff; - dc_settings_end(); - settings_end(); - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &shearwater_cylinders, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query shearwater_cylinders failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_changes_template, cur_dive->number, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &shearwater_changes, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query shearwater_changes failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &shearwater_profile_sample, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query shearwater_profile_sample failed.\n"); - return 1; - } - - dive_end(); - - return SQLITE_OK; -} - -extern int cobalt_cylinders(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - cylinder_start(); - if (data[0]) - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atoi(data[0]) * 10; - if (data[1]) - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atoi(data[1]) * 10; - if (data[2]) - cur_dive->cylinder[cur_cylinder_index].start.mbar = psi_to_mbar(atoi(data[2])); - if (data[3]) - cur_dive->cylinder[cur_cylinder_index].end.mbar = psi_to_mbar(atoi(data[3])); - if (data[4]) - cur_dive->cylinder[cur_cylinder_index].type.size.mliter = atoi(data[4]) * 100; - if (data[5]) - cur_dive->cylinder[cur_cylinder_index].gas_used.mliter = atoi(data[5]) * 1000; - cylinder_end(); - - return 0; -} - -extern int cobalt_buddies(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - if (data[0]) - utf8_string(data[0], &cur_dive->buddy); - - return 0; -} - -/* - * We still need to figure out how to map free text visibility to - * Subsurface star rating. - */ - -extern int cobalt_visibility(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - (void) data; - return 0; -} - -extern int cobalt_location(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - static char *location = NULL; - if (data[0]) { - if (location) { - char *tmp = malloc(strlen(location) + strlen(data[0]) + 4); - if (!tmp) - return -1; - sprintf(tmp, "%s / %s", location, data[0]); - free(location); - location = NULL; - cur_dive->dive_site_uuid = find_or_create_dive_site_with_name(tmp, cur_dive->when); - free(tmp); - } else { - location = strdup(data[0]); - } - } - return 0; -} - - -extern int cobalt_dive(void *param, int columns, char **data, char **column) -{ - (void) columns; - (void) column; - - int retval = 0; - sqlite3 *handle = (sqlite3 *)param; - char *err = NULL; - char get_profile_template[] = "select runtime*60,(DepthPressure*10000/SurfacePressure)-10000,p.Temperature from Dive AS d JOIN TrackPoints AS p ON d.Id=p.DiveId where d.Id=%d"; - char get_cylinder_template[] = "select FO2,FHe,StartingPressure,EndingPressure,TankSize,TankPressure,TotalConsumption from GasMixes where DiveID=%d and StartingPressure>0 group by FO2,FHe"; - char get_buddy_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=4"; - char get_visibility_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=3"; - char get_location_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=0"; - char get_site_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=1"; - char get_buffer[1024]; - - dive_start(); - cur_dive->number = atoi(data[0]); - - cur_dive->when = (time_t)(atol(data[1])); - - if (data[4]) - utf8_string(data[4], &cur_dive->notes); - - /* data[5] should have information on Units used, but I cannot - * parse it at all based on the sample log I have received. The - * temperatures in the samples are all Imperial, so let's go by - * that. - */ - - metric = 0; - - /* Cobalt stores the pressures, not the depth */ - if (data[6]) - cur_dive->dc.maxdepth.mm = atoi(data[6]); - - if (data[7]) - cur_dive->dc.duration.seconds = atoi(data[7]); - - if (data[8]) - cur_dive->dc.surface_pressure.mbar = atoi(data[8]); - /* - * TODO: the deviceid hash should be calculated here. - */ - settings_start(); - dc_settings_start(); - if (data[9]) { - utf8_string(data[9], &cur_settings.dc.serial_nr); - cur_settings.dc.deviceid = atoi(data[9]); - cur_settings.dc.model = strdup("Cobalt import"); - } - - dc_settings_end(); - settings_end(); - - if (data[9]) { - cur_dive->dc.deviceid = atoi(data[9]); - cur_dive->dc.model = strdup("Cobalt import"); - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_cylinders, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_cylinders failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_buddy_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_buddies, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_buddies failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_visibility_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_visibility, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_visibility failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_location_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_location, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_location failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_site_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_location, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_location (site) failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_profile_sample, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_profile_sample failed.\n"); - return 1; - } - - dive_end(); - - return SQLITE_OK; -} - - -int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, - struct dive_table *table) -{ - (void) buffer; - (void) size; - - int retval; - char *err = NULL; - target_table = table; - - char get_dives[] = "select i.diveId,timestamp,location||' / '||site,buddy,notes,imperialUnits,maxDepth,maxTime,startSurfacePressure,computerSerial,computerModel FROM dive_info AS i JOIN dive_logs AS l ON i.diveId=l.diveId"; - - retval = sqlite3_exec(handle, get_dives, &shearwater_dive, handle, &err); - - if (retval != SQLITE_OK) { - fprintf(stderr, "Database query failed '%s'.\n", url); - return 1; - } - - return 0; -} - -int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, - struct dive_table *table) -{ - (void) buffer; - (void) size; - - int retval; - char *err = NULL; - target_table = table; - - char get_dives[] = "select Id,strftime('%s',DiveStartTime),LocationId,'buddy','notes',Units,(MaxDepthPressure*10000/SurfacePressure)-10000,DiveMinutes,SurfacePressure,SerialNumber,'model' from Dive where IsViewDeleted = 0"; - - retval = sqlite3_exec(handle, get_dives, &cobalt_dive, handle, &err); - - if (retval != SQLITE_OK) { - fprintf(stderr, "Database query failed '%s'.\n", url); - return 1; - } - - return 0; -} - -extern int divinglog_cylinder(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - short dbl = 1; - //char get_cylinder_template[] = "select TankID,TankSize,PresS,PresE,PresW,O2,He,DblTank from Tank where LogID = %d"; - - /* - * Divinglog might have more cylinders than what we support. So - * better to ignore those. - */ - - if (cur_cylinder_index >= MAX_CYLINDERS) - return 0; - - if (data[7] && atoi(data[7]) > 0) - dbl = 2; - - cylinder_start(); - - /* - * Assuming that we have to double the cylinder size, if double - * is set - */ - - if (data[1] && atoi(data[1]) > 0) - cur_dive->cylinder[cur_cylinder_index].type.size.mliter = atol(data[1]) * 1000 * dbl; - - if (data[2] && atoi(data[2]) > 0) - cur_dive->cylinder[cur_cylinder_index].start.mbar = atol(data[2]) * 1000; - if (data[3] && atoi(data[3]) > 0) - cur_dive->cylinder[cur_cylinder_index].end.mbar = atol(data[3]) * 1000; - if (data[4] && atoi(data[4]) > 0) - cur_dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = atol(data[4]) * 1000; - if (data[5] && atoi(data[5]) > 0) - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atol(data[5]) * 10; - if (data[6] && atoi(data[6]) > 0) - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atol(data[6]) * 10; - - cylinder_end(); - - return 0; -} - -extern int divinglog_profile(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - int sinterval = 0; - unsigned long i, len, lenprofile2 = 0; - char *ptr, temp[4], pres[5], hbeat[4], stop[4], stime[4], ndl[4], ppo2_1[4], ppo2_2[4], ppo2_3[4], cns[5], setpoint[3]; - short oldcyl = -1; - - /* We do not have samples */ - if (!data[1]) - return 0; - - if (data[0]) - sinterval = atoi(data[0]); - - /* - * Profile - * - * DDDDDCRASWEE - * D: Depth (in meter with two decimals) - * C: Deco (1 = yes, 0 = no) - * R: RBT (Remaining Bottom Time warning) - * A: Ascent warning - * S: Decostop ignored - * W: Work warning - * E: Extra info (different for every computer) - * - * Example: 004500010000 - * 4.5 m, no deco, no RBT warning, ascanding too fast, no decostop ignored, no work, no extra info - * - * - * Profile2 - * - * TTTFFFFIRRR - * - * T: Temperature (in °C with one decimal) - * F: Tank pressure 1 (in bar with one decimal) - * I: Tank ID (0, 1, 2 ... 9) - * R: RBT (in min) - * - * Example: 25518051099 - * 25.5 °C, 180.5 bar, Tank 1, 99 min RBT - * - */ - - len = strlen(data[1]); - - if (data[2]) - lenprofile2 = strlen(data[2]); - - for (i = 0, ptr = data[1]; i * 12 < len; ++i) { - sample_start(); - - cur_sample->time.seconds = sinterval * i; - cur_sample->in_deco = ptr[5] - '0' ? true : false; - ptr[5] = 0; - cur_sample->depth.mm = atoi(ptr) * 10; - - if (i * 11 < lenprofile2) { - memcpy(temp, &data[2][i * 11], 3); - cur_sample->temperature.mkelvin = C_to_mkelvin(atoi(temp) / 10); - } - - if (data[2]) { - memcpy(pres, &data[2][i * 11 + 3], 4); - cur_sample->cylinderpressure.mbar = atoi(pres) * 100; - } - - if (data[3] && strlen(data[3])) { - memcpy(hbeat, &data[3][i * 14 + 8], 3); - cur_sample->heartbeat = atoi(hbeat); - } - - if (data[4] && strlen(data[4])) { - memcpy(stop, &data[4][i * 9 + 6], 3); - cur_sample->stopdepth.mm = atoi(stop) * 1000; - - memcpy(stime, &data[4][i * 9 + 3], 3); - cur_sample->stoptime.seconds = atoi(stime) * 60; - - /* - * Following value is NDL when not in deco, and - * either 0 or TTS when in deco. - */ - - memcpy(ndl, &data[4][i * 9 + 0], 3); - if (cur_sample->in_deco == false) - cur_sample->ndl.seconds = atoi(ndl) * 60; - else if (atoi(ndl)) - cur_sample->tts.seconds = atoi(ndl) * 60; - - if (cur_sample->in_deco == true) - cur_sample->ndl.seconds = 0; - } - - /* - * AAABBBCCCOOOONNNNSS - * - * A = ppO2 cell 1 (measured) - * B = ppO2 cell 2 (measured) - * C = ppO2 cell 3 (measured) - * O = OTU - * N = CNS - * S = Setpoint - * - * Example: 1121131141548026411 - * 1.12 bar, 1.13 bar, 1.14 bar, OTU = 154.8, CNS = 26.4, Setpoint = 1.1 - */ - - if (data[5] && strlen(data[5])) { - memcpy(ppo2_1, &data[5][i * 19 + 0], 3); - memcpy(ppo2_2, &data[5][i * 19 + 3], 3); - memcpy(ppo2_3, &data[5][i * 19 + 6], 3); - memcpy(cns, &data[5][i * 19 + 13], 4); - memcpy(setpoint, &data[5][i * 19 + 17], 2); - - if (atoi(ppo2_1) > 0) - cur_sample->o2sensor[0].mbar = atoi(ppo2_1) * 100; - if (atoi(ppo2_2) > 0) - cur_sample->o2sensor[1].mbar = atoi(ppo2_2) * 100; - if (atoi(ppo2_3) > 0) - cur_sample->o2sensor[2].mbar = atoi(ppo2_3) * 100; - if (atoi(cns) > 0) - cur_sample->cns = rint(atoi(cns) / 10); - if (atoi(setpoint) > 0) - cur_sample->setpoint.mbar = atoi(setpoint) * 100; - - } - - /* - * My best guess is that if we have o2sensors, then it - * is either CCR or PSCR dive. And the first time we - * have O2 sensor readings, we can count them to get - * the amount O2 sensors. - */ - - if (!cur_dive->dc.no_o2sensors) { - cur_dive->dc.no_o2sensors = cur_sample->o2sensor[0].mbar ? 1 : 0 + - cur_sample->o2sensor[1].mbar ? 1 : 0 + - cur_sample->o2sensor[2].mbar ? 1 : 0; - cur_dive->dc.divemode = CCR; - } - - ptr += 12; - sample_end(); - } - - for (i = 0, ptr = data[1]; i * 12 < len; ++i) { - /* Remaining bottom time warning */ - if (ptr[6] - '0') { - event_start(); - cur_event.time.seconds = sinterval * i; - strcpy(cur_event.name, "rbt"); - event_end(); - } - - /* Ascent warning */ - if (ptr[7] - '0') { - event_start(); - cur_event.time.seconds = sinterval * i; - strcpy(cur_event.name, "ascent"); - event_end(); - } - - /* Deco stop ignored */ - if (ptr[8] - '0') { - event_start(); - cur_event.time.seconds = sinterval * i; - strcpy(cur_event.name, "violation"); - event_end(); - } - - /* Workload warning */ - if (ptr[9] - '0') { - event_start(); - cur_event.time.seconds = sinterval * i; - strcpy(cur_event.name, "workload"); - event_end(); - } - ptr += 12; - } - - for (i = 0; i * 11 < lenprofile2; ++i) { - short tank = data[2][i * 11 + 7] - '0'; - if (oldcyl != tank) { - struct gasmix *mix = &cur_dive->cylinder[tank].gasmix; - int o2 = get_o2(mix); - int he = get_he(mix); - - event_start(); - cur_event.time.seconds = sinterval * i; - strcpy(cur_event.name, "gaschange"); - - o2 = (o2 + 5) / 10; - he = (he + 5) / 10; - cur_event.value = o2 + (he << 16); - - event_end(); - oldcyl = tank; - } - } - - return 0; -} - - -extern int divinglog_dive(void *param, int columns, char **data, char **column) -{ - (void) columns; - (void) column; - - int retval = 0; - sqlite3 *handle = (sqlite3 *)param; - char *err = NULL; - char get_profile_template[] = "select ProfileInt,Profile,Profile2,Profile3,Profile4,Profile5 from Logbook where ID = %d"; - char get_cylinder0_template[] = "select 0,TankSize,PresS,PresE,PresW,O2,He,DblTank from Logbook where ID = %d"; - char get_cylinder_template[] = "select TankID,TankSize,PresS,PresE,PresW,O2,He,DblTank from Tank where LogID = %d order by TankID"; - char get_buffer[1024]; - - dive_start(); - diveid = atoi(data[13]); - cur_dive->number = atoi(data[0]); - - cur_dive->when = (time_t)(atol(data[1])); - - if (data[2]) - cur_dive->dive_site_uuid = find_or_create_dive_site_with_name(data[2], cur_dive->when); - - if (data[3]) - utf8_string(data[3], &cur_dive->buddy); - - if (data[4]) - utf8_string(data[4], &cur_dive->notes); - - if (data[5]) - cur_dive->dc.maxdepth.mm = atof(data[5]) * 1000; - - if (data[6]) - cur_dive->dc.duration.seconds = atoi(data[6]) * 60; - - if (data[7]) - utf8_string(data[7], &cur_dive->divemaster); - - if (data[8]) - cur_dive->airtemp.mkelvin = C_to_mkelvin(atol(data[8])); - - if (data[9]) - cur_dive->watertemp.mkelvin = C_to_mkelvin(atol(data[9])); - - if (data[10]) { - cur_dive->weightsystem[0].weight.grams = atol(data[10]) * 1000; - cur_dive->weightsystem[0].description = strdup(translate("gettextFromC", "unknown")); - } - - if (data[11]) - cur_dive->suit = strdup(data[11]); - - settings_start(); - dc_settings_start(); - - if (data[12]) { - cur_dive->dc.model = strdup(data[12]); - } else { - cur_settings.dc.model = strdup("Divinglog import"); - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder0_template, diveid); - retval = sqlite3_exec(handle, get_buffer, &divinglog_cylinder, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query divinglog_cylinder0 failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder_template, diveid); - retval = sqlite3_exec(handle, get_buffer, &divinglog_cylinder, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query divinglog_cylinder failed.\n"); - return 1; - } - - - dc_settings_end(); - settings_end(); - - if (data[12]) { - cur_dive->dc.model = strdup(data[12]); - } else { - cur_dive->dc.model = strdup("Divinglog import"); - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, diveid); - retval = sqlite3_exec(handle, get_buffer, &divinglog_profile, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query divinglog_profile failed.\n"); - return 1; - } - - dive_end(); - - return SQLITE_OK; -} - - -int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, - struct dive_table *table) -{ - (void) buffer; - (void) size; - - int retval; - char *err = NULL; - target_table = table; - - char get_dives[] = "select Number,strftime('%s',Divedate || ' ' || ifnull(Entrytime,'00:00')),Country || ' - ' || City || ' - ' || Place,Buddy,Comments,Depth,Divetime,Divemaster,Airtemp,Watertemp,Weight,Divesuit,Computer,ID from Logbook where UUID not in (select UUID from DeletedRecords)"; - - retval = sqlite3_exec(handle, get_dives, &divinglog_dive, handle, &err); - - if (retval != SQLITE_OK) { - fprintf(stderr, "Database query failed '%s'.\n", url); - return 1; - } - - return 0; -} - -/* - * Parse a unsigned 32-bit integer in little-endian mode, - * that is seconds since Jan 1, 2000. - */ -static timestamp_t parse_dlf_timestamp(unsigned char *buffer) -{ - timestamp_t offset; - - offset = buffer[3]; - offset = (offset << 8) + buffer[2]; - offset = (offset << 8) + buffer[1]; - offset = (offset << 8) + buffer[0]; - - // Jan 1, 2000 is 946684800 seconds after Jan 1, 1970, which is - // the Unix epoch date that "timestamp_t" uses. - return offset + 946684800; -} - -int parse_dlf_buffer(unsigned char *buffer, size_t size) -{ - unsigned char *ptr = buffer; - unsigned char event; - bool found; - unsigned int time = 0; - int i; - char serial[6]; - - target_table = &dive_table; - - // Check for the correct file magic - if (ptr[0] != 'D' || ptr[1] != 'i' || ptr[2] != 'v' || ptr[3] != 'E') - return -1; - - dive_start(); - divecomputer_start(); - - cur_dc->model = strdup("DLF import"); - // (ptr[7] << 8) + ptr[6] Is "Serial" - snprintf(serial, sizeof(serial), "%d", (ptr[7] << 8) + ptr[6]); - cur_dc->serial = strdup(serial); - cur_dc->when = parse_dlf_timestamp(ptr + 8); - cur_dive->when = cur_dc->when; - - cur_dc->duration.seconds = ((ptr[14] & 0xFE) << 16) + (ptr[13] << 8) + ptr[12]; - - // ptr[14] >> 1 is scrubber used in % - - // 3 bit dive type - switch((ptr[15] & 0x30) >> 3) { - case 0: // unknown - case 1: - cur_dc->divemode = OC; - break; - case 2: - cur_dc->divemode = CCR; - break; - case 3: - cur_dc->divemode = CCR; // mCCR - break; - case 4: - cur_dc->divemode = FREEDIVE; - break; - case 5: - cur_dc->divemode = OC; // Gauge - break; - case 6: - cur_dc->divemode = PSCR; // ASCR - break; - case 7: - cur_dc->divemode = PSCR; - break; - } - - cur_dc->maxdepth.mm = ((ptr[21] << 8) + ptr[20]) * 10; - cur_dc->surface_pressure.mbar = ((ptr[25] << 8) + ptr[24]) / 10; - - /* Done with parsing what we know about the dive header */ - ptr += 32; - - // We're going to interpret ppO2 saved as a sensor value in these modes. - if (cur_dc->divemode == CCR || cur_dc->divemode == PSCR) - cur_dc->no_o2sensors = 1; - - while (ptr < buffer + size) { - time = ((ptr[0] >> 4) & 0x0f) + - ((ptr[1] << 4) & 0xff0) + - (ptr[2] & 0x0f) * 3600; /* hours */ - event = ptr[0] & 0x0f; - switch (event) { - case 0: - /* Regular sample */ - sample_start(); - cur_sample->time.seconds = time; - cur_sample->depth.mm = ((ptr[5] << 8) + ptr[4]) * 10; - // Crazy precision on these stored values... - // Only store value if we're in CCR/PSCR mode, - // because we rather calculate ppo2 our selfs. - if (cur_dc->divemode == CCR || cur_dc->divemode == PSCR) - cur_sample->o2sensor[0].mbar = ((ptr[7] << 8) + ptr[6]) / 10; - // NDL in minutes, 10 bit - cur_sample->ndl.seconds = (((ptr[9] & 0x03) << 8) + ptr[8]) * 60; - // TTS in minutes, 10 bit - cur_sample->tts.seconds = (((ptr[10] & 0x0F) << 6) + (ptr[9] >> 2)) * 60; - // Temperature in 1/10 C, 10 bit signed - cur_sample->temperature.mkelvin = ((ptr[11] & 0x20) ? -1 : 1) * (((ptr[11] & 0x1F) << 4) + (ptr[10] >> 4)) * 100 + ZERO_C_IN_MKELVIN; - // ptr[11] & 0xF0 is unknown, and always 0xC in all checked files - cur_sample->stopdepth.mm = ((ptr[13] << 8) + ptr[12]) * 10; - if (cur_sample->stopdepth.mm) - cur_sample->in_deco = true; - //ptr[14] is helium content, always zero? - //ptr[15] is setpoint, always zero? - sample_end(); - break; - case 1: /* dive event */ - case 2: /* automatic parameter change */ - case 3: /* diver error */ - case 4: /* internal error */ - case 5: /* device activity log */ - event_start(); - cur_event.time.seconds = time; - switch (ptr[4]) { - case 1: - strcpy(cur_event.name, "Setpoint Manual"); - // There is a setpoint value somewhere... - break; - case 2: - strcpy(cur_event.name, "Setpoint Auto"); - // There is a setpoint value somewhere... - switch (ptr[7]) { - case 0: - strcat(cur_event.name, " Manual"); - break; - case 1: - strcat(cur_event.name, " Auto Start"); - break; - case 2: - strcat(cur_event.name, " Auto Hypox"); - break; - case 3: - strcat(cur_event.name, " Auto Timeout"); - break; - case 4: - strcat(cur_event.name, " Auto Ascent"); - break; - case 5: - strcat(cur_event.name, " Auto Stall"); - break; - case 6: - strcat(cur_event.name, " Auto SP Low"); - break; - default: - break; - } - break; - case 3: - // obsolete - strcpy(cur_event.name, "OC"); - break; - case 4: - // obsolete - strcpy(cur_event.name, "CCR"); - break; - case 5: - strcpy(cur_event.name, "gaschange"); - cur_event.type = SAMPLE_EVENT_GASCHANGE2; - cur_event.value = ptr[7] << 8 ^ ptr[6]; - - found = false; - for (i = 0; i < cur_cylinder_index; ++i) { - if (cur_dive->cylinder[i].gasmix.o2.permille == ptr[6] * 10 && cur_dive->cylinder[i].gasmix.he.permille == ptr[7] * 10) { - found = true; - break; - } - } - if (!found) { - cylinder_start(); - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = ptr[6] * 10; - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = ptr[7] * 10; - cylinder_end(); - cur_event.gas.index = cur_cylinder_index; - } else { - cur_event.gas.index = i; - } - break; - case 6: - strcpy(cur_event.name, "Start"); - break; - case 7: - strcpy(cur_event.name, "Too Fast"); - break; - case 8: - strcpy(cur_event.name, "Above Ceiling"); - break; - case 9: - strcpy(cur_event.name, "Toxic"); - break; - case 10: - strcpy(cur_event.name, "Hypox"); - break; - case 11: - strcpy(cur_event.name, "Critical"); - break; - case 12: - strcpy(cur_event.name, "Sensor Disabled"); - break; - case 13: - strcpy(cur_event.name, "Sensor Enabled"); - break; - case 14: - strcpy(cur_event.name, "O2 Backup"); - break; - case 15: - strcpy(cur_event.name, "Peer Down"); - break; - case 16: - strcpy(cur_event.name, "HS Down"); - break; - case 17: - strcpy(cur_event.name, "Inconsistent"); - break; - case 18: - // key pressed - probably not - // interesting to view on profile - break; - case 19: - // obsolete - strcpy(cur_event.name, "SCR"); - break; - case 20: - strcpy(cur_event.name, "Above Stop"); - break; - case 21: - strcpy(cur_event.name, "Safety Miss"); - break; - case 22: - strcpy(cur_event.name, "Fatal"); - break; - case 23: - strcpy(cur_event.name, "Diluent"); - break; - case 24: - strcpy(cur_event.name, "gaschange"); - cur_event.type = SAMPLE_EVENT_GASCHANGE2; - cur_event.value = ptr[7] << 8 ^ ptr[6]; - event_end(); - // This is both a mode change and a gas change event - // so we encode it as two separate events. - event_start(); - strcpy(cur_event.name, "Change Mode"); - switch (ptr[8]) { - case 1: - strcat(cur_event.name, ": OC"); - break; - case 2: - strcat(cur_event.name, ": CCR"); - break; - case 3: - strcat(cur_event.name, ": mCCR"); - break; - case 4: - strcat(cur_event.name, ": Free"); - break; - case 5: - strcat(cur_event.name, ": Gauge"); - break; - case 6: - strcat(cur_event.name, ": ASCR"); - break; - case 7: - strcat(cur_event.name, ": PSCR"); - break; - default: - break; - } - event_end(); - break; - case 25: - strcpy(cur_event.name, "CCR O2 solenoid opened/closed"); - break; - case 26: - strcpy(cur_event.name, "User mark"); - break; - case 27: - snprintf(cur_event.name, MAX_EVENT_NAME, "%sGF Switch (%d/%d)", ptr[6] ? "Bailout, ": "", ptr[7], ptr[8]); - break; - case 28: - strcpy(cur_event.name, "Peer Up"); - break; - case 29: - strcpy(cur_event.name, "HS Up"); - break; - case 30: - snprintf(cur_event.name, MAX_EVENT_NAME, "CNS %d%%", ptr[6]); - break; - default: - // No values above 30 had any description - break; - } - event_end(); - break; - case 6: - /* device configuration */ - break; - case 7: - /* measure record */ - /* Po2 sample? Solenoid inject? */ - //fprintf(stderr, "%02X %02X%02X %02X%02X\n", ptr[5], ptr[6], ptr[7], ptr[8], ptr[9]); - break; - default: - /* Unknown... */ - break; - } - ptr += 16; - } - divecomputer_end(); - dive_end(); - return 0; -} - - -void parse_xml_init(void) -{ - LIBXML_TEST_VERSION -} - -void parse_xml_exit(void) -{ - xmlCleanupParser(); -} - -static struct xslt_files { - const char *root; - const char *file; - const char *attribute; -} xslt_files[] = { - { "SUUNTO", "SuuntoSDM.xslt", NULL }, - { "Dive", "SuuntoDM4.xslt", "xmlns" }, - { "Dive", "shearwater.xslt", "version" }, - { "JDiveLog", "jdivelog2subsurface.xslt", NULL }, - { "dives", "MacDive.xslt", NULL }, - { "DIVELOGSDATA", "divelogs.xslt", NULL }, - { "uddf", "uddf.xslt", NULL }, - { "UDDF", "uddf.xslt", NULL }, - { "profile", "udcf.xslt", NULL }, - { "Divinglog", "DivingLog.xslt", NULL }, - { "csv", "csv2xml.xslt", NULL }, - { "sensuscsv", "sensuscsv.xslt", NULL }, - { "SubsurfaceCSV", "subsurfacecsv.xslt", NULL }, - { "manualcsv", "manualcsv2xml.xslt", NULL }, - { "logbook", "DiveLog.xslt", NULL }, - { NULL, } - }; - -static xmlDoc *test_xslt_transforms(xmlDoc *doc, const char **params) -{ - struct xslt_files *info = xslt_files; - xmlDoc *transformed; - xsltStylesheetPtr xslt = NULL; - xmlNode *root_element = xmlDocGetRootElement(doc); - char *attribute; - - while (info->root) { - if ((strcasecmp((const char *)root_element->name, info->root) == 0)) { - if (info->attribute == NULL) - break; - else if (xmlGetProp(root_element, (const xmlChar *)info->attribute) != NULL) - break; - } - info++; - } - - if (info->root) { - attribute = (char *)xmlGetProp(xmlFirstElementChild(root_element), (const xmlChar *)"name"); - if (attribute) { - if (strcasecmp(attribute, "subsurface") == 0) { - free((void *)attribute); - return doc; - } - free((void *)attribute); - } - xmlSubstituteEntitiesDefault(1); - xslt = get_stylesheet(info->file); - if (xslt == NULL) { - report_error(translate("gettextFromC", "Can't open stylesheet %s"), info->file); - return doc; - } - transformed = xsltApplyStylesheet(xslt, doc, params); - xmlFreeDoc(doc); - xsltFreeStylesheet(xslt); - - return transformed; - } - return doc; -} diff --git a/subsurface-core/planner.c b/subsurface-core/planner.c deleted file mode 100644 index 705aad1cb..000000000 --- a/subsurface-core/planner.c +++ /dev/null @@ -1,1471 +0,0 @@ -/* planner.c - * - * code that allows us to plan future dives - * - * (c) Dirk Hohndel 2013 - */ -#include <assert.h> -#include <unistd.h> -#include <ctype.h> -#include <string.h> -#include "dive.h" -#include "deco.h" -#include "divelist.h" -#include "planner.h" -#include "gettext.h" -#include "libdivecomputer/parser.h" - -#define TIMESTEP 2 /* second */ -#define DECOTIMESTEP 60 /* seconds. Unit of deco stop times */ - -int decostoplevels_metric[] = { 0, 3000, 6000, 9000, 12000, 15000, 18000, 21000, 24000, 27000, - 30000, 33000, 36000, 39000, 42000, 45000, 48000, 51000, 54000, 57000, - 60000, 63000, 66000, 69000, 72000, 75000, 78000, 81000, 84000, 87000, - 90000, 100000, 110000, 120000, 130000, 140000, 150000, 160000, 170000, - 180000, 190000, 200000, 220000, 240000, 260000, 280000, 300000, - 320000, 340000, 360000, 380000 }; -int decostoplevels_imperial[] = { 0, 3048, 6096, 9144, 12192, 15240, 18288, 21336, 24384, 27432, - 30480, 33528, 36576, 39624, 42672, 45720, 48768, 51816, 54864, 57912, - 60960, 64008, 67056, 70104, 73152, 76200, 79248, 82296, 85344, 88392, - 91440, 101600, 111760, 121920, 132080, 142240, 152400, 162560, 172720, - 182880, 193040, 203200, 223520, 243840, 264160, 284480, 304800, - 325120, 345440, 365760, 386080 }; - -double plangflow, plangfhigh; -bool plan_verbatim, plan_display_runtime, plan_display_duration, plan_display_transitions; - -pressure_t first_ceiling_pressure, max_bottom_ceiling_pressure = {}; - -const char *disclaimer; - -#if DEBUG_PLAN -void dump_plan(struct diveplan *diveplan) -{ - struct divedatapoint *dp; - struct tm tm; - - if (!diveplan) { - printf("Diveplan NULL\n"); - return; - } - utc_mkdate(diveplan->when, &tm); - - printf("\nDiveplan @ %04d-%02d-%02d %02d:%02d:%02d (surfpres %dmbar):\n", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec, - diveplan->surface_pressure); - dp = diveplan->dp; - while (dp) { - printf("\t%3u:%02u: %dmm gas: %d o2 %d h2\n", FRACTION(dp->time, 60), dp->depth, get_o2(&dp->gasmix), get_he(&dp->gasmix)); - dp = dp->next; - } -} -#endif - -bool diveplan_empty(struct diveplan *diveplan) -{ - struct divedatapoint *dp; - if (!diveplan || !diveplan->dp) - return true; - dp = diveplan->dp; - while (dp) { - if (dp->time) - return false; - dp = dp->next; - } - return true; -} - -/* get the gas at a certain time during the dive */ -void get_gas_at_time(struct dive *dive, struct divecomputer *dc, duration_t time, struct gasmix *gas) -{ - // we always start with the first gas, so that's our gas - // unless an event tells us otherwise - struct event *event = dc->events; - *gas = dive->cylinder[0].gasmix; - while (event && event->time.seconds <= time.seconds) { - if (!strcmp(event->name, "gaschange")) { - int cylinder_idx = get_cylinder_index(dive, event); - *gas = dive->cylinder[cylinder_idx].gasmix; - } - event = event->next; - } -} - -int get_gasidx(struct dive *dive, struct gasmix *mix) -{ - int gasidx = -1; - - while (++gasidx < MAX_CYLINDERS) - if (gasmix_distance(&dive->cylinder[gasidx].gasmix, mix) < 100) - return gasidx; - return -1; -} - -void interpolate_transition(struct dive *dive, duration_t t0, duration_t t1, depth_t d0, depth_t d1, const struct gasmix *gasmix, o2pressure_t po2) -{ - uint32_t j; - - for (j = t0.seconds; j < t1.seconds; j++) { - int depth = interpolate(d0.mm, d1.mm, j - t0.seconds, t1.seconds - t0.seconds); - add_segment(depth_to_bar(depth, dive), gasmix, 1, po2.mbar, dive, prefs.bottomsac); - } - if (d1.mm > d0.mm) - calc_crushing_pressure(depth_to_bar(d1.mm, &displayed_dive)); -} - -/* returns the tissue tolerance at the end of this (partial) dive */ -void tissue_at_end(struct dive *dive, char **cached_datap) -{ - struct divecomputer *dc; - struct sample *sample, *psample; - int i; - depth_t lastdepth = {}; - duration_t t0 = {}, t1 = {}; - struct gasmix gas; - - if (!dive) - return; - if (*cached_datap) { - restore_deco_state(*cached_datap); - } else { - init_decompression(dive); - cache_deco_state(cached_datap); - } - dc = &dive->dc; - if (!dc->samples) - return; - psample = sample = dc->sample; - - for (i = 0; i < dc->samples; i++, sample++) { - o2pressure_t setpoint; - - if (i) - setpoint = sample[-1].setpoint; - else - setpoint = sample[0].setpoint; - - t1 = sample->time; - get_gas_at_time(dive, dc, t0, &gas); - if (i > 0) - lastdepth = psample->depth; - - /* The ceiling in the deeper portion of a multilevel dive is sometimes critical for the VPM-B - * Boyle's law compensation. We should check the ceiling prior to ascending during the bottom - * portion of the dive. The maximum ceiling might be reached while ascending, but testing indicates - * that it is only marginally deeper than the ceiling at the start of ascent. - * Do not set the first_ceiling_pressure variable (used for the Boyle's law compensation calculation) - * at this stage, because it would interfere with calculating the ceiling at the end of the bottom - * portion of the dive. - * Remember the value for later. - */ - if ((prefs.deco_mode == VPMB) && (lastdepth.mm > sample->depth.mm)) { - pressure_t ceiling_pressure; - nuclear_regeneration(t0.seconds); - vpmb_start_gradient(); - ceiling_pressure.mbar = depth_to_mbar(deco_allowed_depth(tissue_tolerance_calc(dive, - depth_to_bar(lastdepth.mm, dive)), - dive->surface_pressure.mbar / 1000.0, - dive, - 1), - dive); - if (ceiling_pressure.mbar > max_bottom_ceiling_pressure.mbar) - max_bottom_ceiling_pressure.mbar = ceiling_pressure.mbar; - } - - interpolate_transition(dive, t0, t1, lastdepth, sample->depth, &gas, setpoint); - psample = sample; - t0 = t1; - } -} - - -/* if a default cylinder is set, use that */ -void fill_default_cylinder(cylinder_t *cyl) -{ - const char *cyl_name = prefs.default_cylinder; - struct tank_info_t *ti = tank_info; - pressure_t pO2 = {.mbar = 1600}; - - if (!cyl_name) - return; - while (ti->name != NULL) { - if (strcmp(ti->name, cyl_name) == 0) - break; - ti++; - } - if (ti->name == NULL) - /* didn't find it */ - return; - cyl->type.description = strdup(ti->name); - if (ti->ml) { - cyl->type.size.mliter = ti->ml; - cyl->type.workingpressure.mbar = ti->bar * 1000; - } else { - cyl->type.workingpressure.mbar = psi_to_mbar(ti->psi); - if (ti->psi) - cyl->type.size.mliter = cuft_to_l(ti->cuft) * 1000 / bar_to_atm(psi_to_bar(ti->psi)); - } - // MOD of air - cyl->depth = gas_mod(&cyl->gasmix, pO2, &displayed_dive, 1); -} - -/* make sure that the gas we are switching to is represented in our - * list of cylinders */ -static int verify_gas_exists(struct gasmix mix_in) -{ - int i; - cylinder_t *cyl; - - for (i = 0; i < MAX_CYLINDERS; i++) { - cyl = displayed_dive.cylinder + i; - if (cylinder_nodata(cyl)) - continue; - if (gasmix_distance(&cyl->gasmix, &mix_in) < 100) - return i; - } - fprintf(stderr, "this gas %s should have been on the cylinder list\nThings will fail now\n", gasname(&mix_in)); - return -1; -} - -/* calculate the new end pressure of the cylinder, based on its current end pressure and the - * latest segment. */ -static void update_cylinder_pressure(struct dive *d, int old_depth, int new_depth, int duration, int sac, cylinder_t *cyl, bool in_deco) -{ - volume_t gas_used; - pressure_t delta_p; - depth_t mean_depth; - int factor = 1000; - - if (d->dc.divemode == PSCR) - factor = prefs.pscr_ratio; - - if (!cyl) - return; - mean_depth.mm = (old_depth + new_depth) / 2; - gas_used.mliter = depth_to_atm(mean_depth.mm, d) * sac / 60 * duration * factor / 1000; - cyl->gas_used.mliter += gas_used.mliter; - if (in_deco) - cyl->deco_gas_used.mliter += gas_used.mliter; - if (cyl->type.size.mliter) { - delta_p.mbar = gas_used.mliter * 1000.0 / cyl->type.size.mliter; - cyl->end.mbar -= delta_p.mbar; - } -} - -/* simply overwrite the data in the displayed_dive - * return false if something goes wrong */ -static void create_dive_from_plan(struct diveplan *diveplan, bool track_gas) -{ - struct divedatapoint *dp; - struct divecomputer *dc; - struct sample *sample; - struct gasmix oldgasmix; - struct event *ev; - cylinder_t *cyl; - int oldpo2 = 0; - int lasttime = 0; - int lastdepth = 0; - enum dive_comp_type type = displayed_dive.dc.divemode; - - if (!diveplan || !diveplan->dp) - return; -#if DEBUG_PLAN & 4 - printf("in create_dive_from_plan\n"); - dump_plan(diveplan); -#endif - displayed_dive.salinity = diveplan->salinity; - // reset the cylinders and clear out the samples and events of the - // displayed dive so we can restart - reset_cylinders(&displayed_dive, track_gas); - dc = &displayed_dive.dc; - dc->when = displayed_dive.when = diveplan->when; - free(dc->sample); - dc->sample = NULL; - dc->samples = 0; - dc->alloc_samples = 0; - while ((ev = dc->events)) { - dc->events = dc->events->next; - free(ev); - } - dp = diveplan->dp; - cyl = &displayed_dive.cylinder[0]; - oldgasmix = cyl->gasmix; - sample = prepare_sample(dc); - sample->setpoint.mbar = dp->setpoint; - sample->sac.mliter = prefs.bottomsac; - oldpo2 = dp->setpoint; - if (track_gas && cyl->type.workingpressure.mbar) - sample->cylinderpressure.mbar = cyl->end.mbar; - sample->manually_entered = true; - finish_sample(dc); - while (dp) { - struct gasmix gasmix = dp->gasmix; - int po2 = dp->setpoint; - if (dp->setpoint) - type = CCR; - int time = dp->time; - int depth = dp->depth; - - if (time == 0) { - /* special entries that just inform the algorithm about - * additional gases that are available */ - if (verify_gas_exists(gasmix) < 0) - goto gas_error_exit; - dp = dp->next; - continue; - } - - /* Check for SetPoint change */ - if (oldpo2 != po2) { - /* this is a bad idea - we should get a different SAMPLE_EVENT type - * reserved for this in libdivecomputer... overloading SMAPLE_EVENT_PO2 - * with a different meaning will only cause confusion elsewhere in the code */ - add_event(dc, lasttime, SAMPLE_EVENT_PO2, 0, po2, "SP change"); - oldpo2 = po2; - } - - /* Make sure we have the new gas, and create a gas change event */ - if (gasmix_distance(&gasmix, &oldgasmix) > 0) { - int idx; - if ((idx = verify_gas_exists(gasmix)) < 0) - goto gas_error_exit; - /* need to insert a first sample for the new gas */ - add_gas_switch_event(&displayed_dive, dc, lasttime + 1, idx); - cyl = &displayed_dive.cylinder[idx]; - sample = prepare_sample(dc); - sample[-1].setpoint.mbar = po2; - sample->time.seconds = lasttime + 1; - sample->depth.mm = lastdepth; - sample->manually_entered = dp->entered; - sample->sac.mliter = dp->entered ? prefs.bottomsac : prefs.decosac; - if (track_gas && cyl->type.workingpressure.mbar) - sample->cylinderpressure.mbar = cyl->sample_end.mbar; - finish_sample(dc); - oldgasmix = gasmix; - } - /* Create sample */ - sample = prepare_sample(dc); - /* set po2 at beginning of this segment */ - /* and keep it valid for last sample - where it likely doesn't matter */ - sample[-1].setpoint.mbar = po2; - sample->setpoint.mbar = po2; - sample->time.seconds = lasttime = time; - sample->depth.mm = lastdepth = depth; - sample->manually_entered = dp->entered; - sample->sac.mliter = dp->entered ? prefs.bottomsac : prefs.decosac; - if (track_gas && !sample[-1].setpoint.mbar) { /* Don't track gas usage for CCR legs of dive */ - update_cylinder_pressure(&displayed_dive, sample[-1].depth.mm, depth, time - sample[-1].time.seconds, - dp->entered ? diveplan->bottomsac : diveplan->decosac, cyl, !dp->entered); - if (cyl->type.workingpressure.mbar) - sample->cylinderpressure.mbar = cyl->end.mbar; - } - finish_sample(dc); - dp = dp->next; - } - dc->divemode = type; -#if DEBUG_PLAN & 32 - save_dive(stdout, &displayed_dive); -#endif - return; - -gas_error_exit: - report_error(translate("gettextFromC", "Too many gas mixes")); - return; -} - -void free_dps(struct diveplan *diveplan) -{ - if (!diveplan) - return; - struct divedatapoint *dp = diveplan->dp; - while (dp) { - struct divedatapoint *ndp = dp->next; - free(dp); - dp = ndp; - } - diveplan->dp = NULL; -} - -struct divedatapoint *create_dp(int time_incr, int depth, struct gasmix gasmix, int po2) -{ - struct divedatapoint *dp; - - dp = malloc(sizeof(struct divedatapoint)); - dp->time = time_incr; - dp->depth = depth; - dp->gasmix = gasmix; - dp->setpoint = po2; - dp->entered = false; - dp->next = NULL; - return dp; -} - -void add_to_end_of_diveplan(struct diveplan *diveplan, struct divedatapoint *dp) -{ - struct divedatapoint **lastdp = &diveplan->dp; - struct divedatapoint *ldp = *lastdp; - int lasttime = 0; - while (*lastdp) { - ldp = *lastdp; - if (ldp->time > lasttime) - lasttime = ldp->time; - lastdp = &(*lastdp)->next; - } - *lastdp = dp; - if (ldp && dp->time != 0) - dp->time += lasttime; -} - -struct divedatapoint *plan_add_segment(struct diveplan *diveplan, int duration, int depth, struct gasmix gasmix, int po2, bool entered) -{ - struct divedatapoint *dp = create_dp(duration, depth, gasmix, po2); - dp->entered = entered; - add_to_end_of_diveplan(diveplan, dp); - return (dp); -} - -struct gaschanges { - int depth; - int gasidx; -}; - - -static struct gaschanges *analyze_gaslist(struct diveplan *diveplan, int *gaschangenr, int depth, int *asc_cylinder) -{ - struct gasmix gas; - int nr = 0; - struct gaschanges *gaschanges = NULL; - struct divedatapoint *dp = diveplan->dp; - int best_depth = displayed_dive.cylinder[*asc_cylinder].depth.mm; - while (dp) { - if (dp->time == 0) { - gas = dp->gasmix; - if (dp->depth <= depth) { - int i = 0; - nr++; - gaschanges = realloc(gaschanges, nr * sizeof(struct gaschanges)); - while (i < nr - 1) { - if (dp->depth < gaschanges[i].depth) { - memmove(gaschanges + i + 1, gaschanges + i, (nr - i - 1) * sizeof(struct gaschanges)); - break; - } - i++; - } - gaschanges[i].depth = dp->depth; - gaschanges[i].gasidx = get_gasidx(&displayed_dive, &gas); - assert(gaschanges[i].gasidx != -1); - } else { - /* is there a better mix to start deco? */ - if (dp->depth < best_depth) { - best_depth = dp->depth; - *asc_cylinder = get_gasidx(&displayed_dive, &gas); - } - } - } - dp = dp->next; - } - *gaschangenr = nr; -#if DEBUG_PLAN & 16 - for (nr = 0; nr < *gaschangenr; nr++) { - int idx = gaschanges[nr].gasidx; - printf("gaschange nr %d: @ %5.2lfm gasidx %d (%s)\n", nr, gaschanges[nr].depth / 1000.0, - idx, gasname(&displayed_dive.cylinder[idx].gasmix)); - } -#endif - return gaschanges; -} - -/* sort all the stops into one ordered list */ -static int *sort_stops(int *dstops, int dnr, struct gaschanges *gstops, int gnr) -{ - int i, gi, di; - int total = dnr + gnr; - int *stoplevels = malloc(total * sizeof(int)); - - /* no gaschanges */ - if (gnr == 0) { - memcpy(stoplevels, dstops, dnr * sizeof(int)); - return stoplevels; - } - i = total - 1; - gi = gnr - 1; - di = dnr - 1; - while (i >= 0) { - if (dstops[di] > gstops[gi].depth) { - stoplevels[i] = dstops[di]; - di--; - } else if (dstops[di] == gstops[gi].depth) { - stoplevels[i] = dstops[di]; - di--; - gi--; - } else { - stoplevels[i] = gstops[gi].depth; - gi--; - } - i--; - if (di < 0) { - while (gi >= 0) - stoplevels[i--] = gstops[gi--].depth; - break; - } - if (gi < 0) { - while (di >= 0) - stoplevels[i--] = dstops[di--]; - break; - } - } - while (i >= 0) - stoplevels[i--] = 0; - -#if DEBUG_PLAN & 16 - int k; - for (k = gnr + dnr - 1; k >= 0; k--) { - printf("stoplevel[%d]: %5.2lfm\n", k, stoplevels[k] / 1000.0); - if (stoplevels[k] == 0) - break; - } -#endif - return stoplevels; -} - -static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, int error) -{ - const unsigned int sz_buffer = 2000000; - const unsigned int sz_temp = 100000; - char *buffer = (char *)malloc(sz_buffer); - char *temp = (char *)malloc(sz_temp); - char *deco, *segmentsymbol; - static char buf[1000]; - int len, lastdepth = 0, lasttime = 0, lastsetpoint = -1, newdepth = 0, lastprintdepth = 0, lastprintsetpoint = -1; - struct gasmix lastprintgasmix = {{ -1 }, { -1 }}; - struct divedatapoint *dp = diveplan->dp; - bool gaschange_after = !plan_verbatim; - bool gaschange_before; - bool lastentered = true; - struct divedatapoint *nextdp = NULL; - - plan_verbatim = prefs.verbatim_plan; - plan_display_runtime = prefs.display_runtime; - plan_display_duration = prefs.display_duration; - plan_display_transitions = prefs.display_transitions; - - if (prefs.deco_mode == VPMB) { - deco = "VPM-B"; - } else { - deco = "BUHLMANN"; - } - - snprintf(buf, sizeof(buf), translate("gettextFromC", "DISCLAIMER / WARNING: THIS IS A NEW IMPLEMENTATION OF THE %s " - "ALGORITHM AND A DIVE PLANNER IMPLEMENTATION BASED ON THAT WHICH HAS " - "RECEIVED ONLY A LIMITED AMOUNT OF TESTING. WE STRONGLY RECOMMEND NOT TO " - "PLAN DIVES SIMPLY BASED ON THE RESULTS GIVEN HERE."), deco); - disclaimer = buf; - - if (!dp) { - free((void *)buffer); - free((void *)temp); - return; - } - - if (error) { - snprintf(temp, sz_temp, "%s", - translate("gettextFromC", "Decompression calculation aborted due to excessive time")); - snprintf(buffer, sz_buffer, "<span style='color: red;'>%s </span> %s<br>", - translate("gettextFromC", "Warning:"), temp); - dive->notes = strdup(buffer); - - free((void *)buffer); - free((void *)temp); - return; - } - - len = show_disclaimer ? snprintf(buffer, sz_buffer, "<div><b>%s<b></div><br>", disclaimer) : 0; - if (prefs.deco_mode == BUEHLMANN){ - snprintf(temp, sz_temp, translate("gettextFromC", "based on Bühlmann ZHL-16B with GFlow = %d and GFhigh = %d"), - diveplan->gflow, diveplan->gfhigh); - } else if (prefs.deco_mode == VPMB){ - if (prefs.conservatism_level == 0) - snprintf(temp, sz_temp, "%s", translate("gettextFromC", "based on VPM-B at nominal conservatism")); - else - snprintf(temp, sz_temp, translate("gettextFromC", "based on VPM-B at +%d conservatism"), prefs.conservatism_level); - } else if (prefs.deco_mode == RECREATIONAL){ - snprintf(temp, sz_temp, translate("gettextFromC", "recreational mode based on Bühlmann ZHL-16B with GFlow = %d and GFhigh = %d"), - diveplan->gflow, diveplan->gfhigh); - } - len += snprintf(buffer + len, sz_buffer - len, "<div><b>%s</b><br>%s</div><br>", - translate("gettextFromC", "Subsurface dive plan"), temp); - - if (!plan_verbatim) { - len += snprintf(buffer + len, sz_buffer - len, "<div><table><thead><tr><th></th><th>%s</th>", - translate("gettextFromC", "depth")); - if (plan_display_duration) - len += snprintf(buffer + len, sz_buffer - len, "<th style='padding-left: 10px;'>%s</th>", - translate("gettextFromC", "duration")); - if (plan_display_runtime) - len += snprintf(buffer + len, sz_buffer - len, "<th style='padding-left: 10px;'>%s</th>", - translate("gettextFromC", "runtime")); - len += snprintf(buffer + len, sz_buffer - len, - "<th style='padding-left: 10px; float: left;'>%s</th></tr></thead><tbody style='float: left;'>", - translate("gettextFromC", "gas")); - } - do { - struct gasmix gasmix, newgasmix = {}; - const char *depth_unit; - double depthvalue; - int decimals; - bool isascent = (dp->depth < lastdepth); - - nextdp = dp->next; - if (dp->time == 0) - continue; - gasmix = dp->gasmix; - depthvalue = get_depth_units(dp->depth, &decimals, &depth_unit); - /* analyze the dive points ahead */ - while (nextdp && nextdp->time == 0) - nextdp = nextdp->next; - if (nextdp) - newgasmix = nextdp->gasmix; - gaschange_after = (nextdp && (gasmix_distance(&gasmix, &newgasmix) || dp->setpoint != nextdp->setpoint)); - gaschange_before = (gasmix_distance(&lastprintgasmix, &gasmix) || lastprintsetpoint != dp->setpoint); - /* do we want to skip this leg as it is devoid of anything useful? */ - if (!dp->entered && - nextdp && - dp->depth != lastdepth && - nextdp->depth != dp->depth && - !gaschange_before && - !gaschange_after) - continue; - if (dp->time - lasttime < 10 && !(gaschange_after && dp->next && dp->depth != dp->next->depth)) - continue; - - len = strlen(buffer); - if (plan_verbatim) { - /* When displaying a verbatim plan, we output a waypoint for every gas change. - * Therefore, we do not need to test for difficult cases that mean we need to - * print a segment just so we don't miss a gas change. This makes the logic - * to determine whether or not to print a segment much simpler than with the - * non-verbatim plan. - */ - if (dp->depth != lastprintdepth) { - if (plan_display_transitions || dp->entered || !dp->next || (gaschange_after && dp->next && dp->depth != nextdp->depth)) { - if (dp->setpoint) - snprintf(temp, sz_temp, translate("gettextFromC", "Transition to %.*f %s in %d:%02d min - runtime %d:%02u on %s (SP = %.1fbar)"), - decimals, depthvalue, depth_unit, - FRACTION(dp->time - lasttime, 60), - FRACTION(dp->time, 60), - gasname(&gasmix), - (double) dp->setpoint / 1000.0); - - else - snprintf(temp, sz_temp, translate("gettextFromC", "Transition to %.*f %s in %d:%02d min - runtime %d:%02u on %s"), - decimals, depthvalue, depth_unit, - FRACTION(dp->time - lasttime, 60), - FRACTION(dp->time, 60), - gasname(&gasmix)); - - len += snprintf(buffer + len, sz_buffer - len, "%s<br>", temp); - } - newdepth = dp->depth; - lasttime = dp->time; - } else { - if ((nextdp && dp->depth != nextdp->depth) || gaschange_after) { - if (dp->setpoint) - snprintf(temp, sz_temp, translate("gettextFromC", "Stay at %.*f %s for %d:%02d min - runtime %d:%02u on %s (SP = %.1fbar)"), - decimals, depthvalue, depth_unit, - FRACTION(dp->time - lasttime, 60), - FRACTION(dp->time, 60), - gasname(&gasmix), - (double) dp->setpoint / 1000.0); - else - snprintf(temp, sz_temp, translate("gettextFromC", "Stay at %.*f %s for %d:%02d min - runtime %d:%02u on %s"), - decimals, depthvalue, depth_unit, - FRACTION(dp->time - lasttime, 60), - FRACTION(dp->time, 60), - gasname(&gasmix)); - - len += snprintf(buffer + len, sz_buffer - len, "%s<br>", temp); - newdepth = dp->depth; - lasttime = dp->time; - } - } - } else { - /* When not displaying the verbatim dive plan, we typically ignore ascents between deco stops, - * unless the display transitions option has been selected. We output a segment if any of the - * following conditions are met. - * 1) Display transitions is selected - * 2) The segment was manually entered - * 3) It is the last segment of the dive - * 4) The segment is not an ascent, there was a gas change at the start of the segment and the next segment - * is a change in depth (typical deco stop) - * 5) There is a gas change at the end of the segment and the last segment was entered (first calculated - * segment if it ends in a gas change) - * 6) There is a gaschange after but no ascent. This should only occur when backgas breaks option is selected - * 7) It is an ascent ending with a gas change, but is not followed by a stop. As case 5 already matches - * the first calculated ascent if it ends with a gas change, this should only occur if a travel gas is - * used for a calculated ascent, there is a subsequent gas change before the first deco stop, and zero - * time has been allowed for a gas switch. - */ - if (plan_display_transitions || dp->entered || !dp->next || - (nextdp && dp->depth != nextdp->depth) || - (!isascent && gaschange_before && nextdp && dp->depth != nextdp->depth) || - (gaschange_after && lastentered) || (gaschange_after && !isascent) || - (isascent && gaschange_after && nextdp && dp->depth != nextdp->depth )) { - // Print a symbol to indicate whether segment is an ascent, descent, constant depth (user entered) or deco stop - if (isascent) - segmentsymbol = "➚"; // up-right arrow for ascent - else if (dp->depth > lastdepth) - segmentsymbol = "➘"; // down-right arrow for descent - else if (dp->entered) - segmentsymbol = "➙"; // right arrow for entered entered segment at constant depth - else - segmentsymbol = "➖"; // heavey minus sign for deco stop - - len += snprintf(buffer + len, sz_buffer - len, "<tr><td style='padding-left: 10px; float: right;'>%s</td>", segmentsymbol); - - snprintf(temp, sz_temp, translate("gettextFromC", "%3.0f%s"), depthvalue, depth_unit); - len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; float: right;'>%s</td>", temp); - if (plan_display_duration) { - snprintf(temp, sz_temp, translate("gettextFromC", "%3dmin"), (dp->time - lasttime + 30) / 60); - len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; float: right;'>%s</td>", temp); - } - if (plan_display_runtime) { - snprintf(temp, sz_temp, translate("gettextFromC", "%3dmin"), (dp->time + 30) / 60); - len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; float: right;'>%s</td>", temp); - } - - /* Normally a gas change is displayed on the stopping segment, so only display a gas change at the end of - * an ascent segment if it is not followed by a stop - */ - if (isascent && gaschange_after && dp->next && nextdp && dp->depth != nextdp->depth) { - if (dp->setpoint) { - snprintf(temp, sz_temp, translate("gettextFromC", "(SP = %.1fbar)"), (double) nextdp->setpoint / 1000.0); - len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; color: red; float: left;'><b>%s %s</b></td>", gasname(&newgasmix), - temp); - } else { - len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; color: red; float: left;'><b>%s</b></td>", gasname(&newgasmix)); - } - lastprintsetpoint = nextdp->setpoint; - lastprintgasmix = newgasmix; - gaschange_after = false; - } else if (gaschange_before) { - // If a new gas has been used for this segment, now is the time to show it - if (dp->setpoint) { - snprintf(temp, sz_temp, translate("gettextFromC", "(SP = %.1fbar)"), (double) dp->setpoint / 1000.0); - len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; color: red; float: left;'><b>%s %s</b></td>", gasname(&gasmix), - temp); - } else { - len += snprintf(buffer + len, sz_buffer - len, "<td style='padding-left: 10px; color: red; float: left;'><b>%s</b></td>", gasname(&gasmix)); - } - // Set variables so subsequent iterations can test against the last gas printed - lastprintsetpoint = dp->setpoint; - lastprintgasmix = gasmix; - gaschange_after = false; - } else { - len += snprintf(buffer + len, sz_buffer - len, "<td> </td>"); - } - len += snprintf(buffer + len, sz_buffer - len, "</tr>"); - newdepth = dp->depth; - lasttime = dp->time; - } - } - if (gaschange_after) { - // gas switch at this waypoint - if (plan_verbatim) { - if (lastsetpoint >= 0) { - if (nextdp && nextdp->setpoint) - snprintf(temp, sz_temp, translate("gettextFromC", "Switch gas to %s (SP = %.1fbar)"), gasname(&newgasmix), (double) nextdp->setpoint / 1000.0); - else - snprintf(temp, sz_temp, translate("gettextFromC", "Switch gas to %s"), gasname(&newgasmix)); - - len += snprintf(buffer + len, sz_buffer - len, "%s<br>", temp); - } - gaschange_after = false; - gasmix = newgasmix; - } - } - lastprintdepth = newdepth; - lastdepth = dp->depth; - lastsetpoint = dp->setpoint; - lastentered = dp->entered; - } while ((dp = nextdp) != NULL); - if (!plan_verbatim) - len += snprintf(buffer + len, sz_buffer - len, "</tbody></table></div>"); - - dive->cns = 0; - dive->maxcns = 0; - update_cylinder_related_info(dive); - snprintf(temp, sz_temp, "%s", translate("gettextFromC", "CNS")); - len += snprintf(buffer + len, sz_buffer - len, "<div><br>%s: %i%%", temp, dive->cns); - snprintf(temp, sz_temp, "%s", translate("gettextFromC", "OTU")); - len += snprintf(buffer + len, sz_buffer - len, "<br>%s: %i</div>", temp, dive->otu); - - if (dive->dc.divemode == CCR) - snprintf(temp, sz_temp, "%s", translate("gettextFromC", "Gas consumption (CCR legs excluded):")); - else - snprintf(temp, sz_temp, "%s", translate("gettextFromC", "Gas consumption:")); - len += snprintf(buffer + len, sz_buffer - len, "<div><br>%s<br>", temp); - for (int gasidx = 0; gasidx < MAX_CYLINDERS; gasidx++) { - double volume, pressure, deco_volume, deco_pressure; - const char *unit, *pressure_unit; - char warning[1000] = ""; - cylinder_t *cyl = &dive->cylinder[gasidx]; - if (cylinder_none(cyl)) - break; - - volume = get_volume_units(cyl->gas_used.mliter, NULL, &unit); - deco_volume = get_volume_units(cyl->deco_gas_used.mliter, NULL, &unit); - if (cyl->type.size.mliter) { - deco_pressure = get_pressure_units(1000.0 * cyl->deco_gas_used.mliter / cyl->type.size.mliter, &pressure_unit); - pressure = get_pressure_units(1000.0 * cyl->gas_used.mliter / cyl->type.size.mliter, &pressure_unit); - /* Warn if the plan uses more gas than is available in a cylinder - * This only works if we have working pressure for the cylinder - * 10bar is a made up number - but it seemed silly to pretend you could breathe cylinder down to 0 */ - if (cyl->end.mbar < 10000) - snprintf(warning, sizeof(warning), " — <span style='color: red;'>%s </span> %s", - translate("gettextFromC", "Warning:"), - translate("gettextFromC", "this is more gas than available in the specified cylinder!")); - else - if ((float) cyl->end.mbar * cyl->type.size.mliter / 1000.0 < (float) cyl->deco_gas_used.mliter) - snprintf(warning, sizeof(warning), " — <span style='color: red;'>%s </span> %s", - translate("gettextFromC", "Warning:"), - translate("gettextFromC", "not enough reserve for gas sharing on ascent!")); - - snprintf(temp, sz_temp, translate("gettextFromC", "%.0f%s/%.0f%s of %s (%.0f%s/%.0f%s in planned ascent)"), volume, unit, pressure, pressure_unit, gasname(&cyl->gasmix), deco_volume, unit, deco_pressure, pressure_unit); - } else { - snprintf(temp, sz_temp, translate("gettextFromC", "%.0f%s (%.0f%s during planned ascent) of %s"), volume, unit, deco_volume, unit, gasname(&cyl->gasmix)); - } - len += snprintf(buffer + len, sz_buffer - len, "%s%s<br>", temp, warning); - } - dp = diveplan->dp; - if (dive->dc.divemode != CCR) { - while (dp) { - if (dp->time != 0) { - struct gas_pressures pressures; - fill_pressures(&pressures, depth_to_atm(dp->depth, dive), &dp->gasmix, 0.0, dive->dc.divemode); - - if (pressures.o2 > (dp->entered ? prefs.bottompo2 : prefs.decopo2) / 1000.0) { - const char *depth_unit; - int decimals; - double depth_value = get_depth_units(dp->depth, &decimals, &depth_unit); - len = strlen(buffer); - snprintf(temp, sz_temp, - translate("gettextFromC", "high pOâ‚‚ value %.2f at %d:%02u with gas %s at depth %.*f %s"), - pressures.o2, FRACTION(dp->time, 60), gasname(&dp->gasmix), decimals, depth_value, depth_unit); - len += snprintf(buffer + len, sz_buffer - len, "<span style='color: red;'>%s </span> %s<br>", - translate("gettextFromC", "Warning:"), temp); - } else if (pressures.o2 < 0.16) { - const char *depth_unit; - int decimals; - double depth_value = get_depth_units(dp->depth, &decimals, &depth_unit); - len = strlen(buffer); - snprintf(temp, sz_temp, - translate("gettextFromC", "low pOâ‚‚ value %.2f at %d:%02u with gas %s at depth %.*f %s"), - pressures.o2, FRACTION(dp->time, 60), gasname(&dp->gasmix), decimals, depth_value, depth_unit); - len += snprintf(buffer + len, sz_buffer - len, "<span style='color: red;'>%s </span> %s<br>", - translate("gettextFromC", "Warning:"), temp); - - } - } - dp = dp->next; - } - } - snprintf(buffer + len, sz_buffer - len, "</div>"); - dive->notes = strdup(buffer); - - free((void *)buffer); - free((void *)temp); -} - -int ascent_velocity(int depth, int avg_depth, int bottom_time) -{ - (void) bottom_time; - /* We need to make this configurable */ - - /* As an example (and possibly reasonable default) this is the Tech 1 provedure according - * to http://www.globalunderwaterexplorers.org/files/Standards_and_Procedures/SOP_Manual_Ver2.0.2.pdf */ - - if (depth * 4 > avg_depth * 3) { - return prefs.ascrate75; - } else { - if (depth * 2 > avg_depth) { - return prefs.ascrate50; - } else { - if (depth > 6000) - return prefs.ascratestops; - else - return prefs.ascratelast6m; - } - } -} - -void track_ascent_gas(int depth, cylinder_t *cylinder, int avg_depth, int bottom_time, bool safety_stop) -{ - while (depth > 0) { - int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; - if (deltad > depth) - deltad = depth; - update_cylinder_pressure(&displayed_dive, depth, depth - deltad, TIMESTEP, prefs.decosac, cylinder, true); - if (depth <= 5000 && depth >= (5000 - deltad) && safety_stop) { - update_cylinder_pressure(&displayed_dive, 5000, 5000, 180, prefs.decosac, cylinder, true); - safety_stop = false; - } - depth -= deltad; - } -} - -// Determine whether ascending to the next stop will break the ceiling. Return true if the ascent is ok, false if it isn't. -bool trial_ascent(int trial_depth, int stoplevel, int avg_depth, int bottom_time, struct gasmix *gasmix, int po2, double surface_pressure) -{ - - bool clear_to_ascend = true; - char *trial_cache = NULL; - - // For consistency with other VPM-B implementations, we should not start the ascent while the ceiling is - // deeper than the next stop (thus the offgasing during the ascent is ignored). - // However, we still need to make sure we don't break the ceiling due to on-gassing during ascent. - if (prefs.deco_mode == VPMB && (deco_allowed_depth(tissue_tolerance_calc(&displayed_dive, - depth_to_bar(stoplevel, &displayed_dive)), - surface_pressure, &displayed_dive, 1) > stoplevel)) - return false; - - cache_deco_state(&trial_cache); - while (trial_depth > stoplevel) { - int deltad = ascent_velocity(trial_depth, avg_depth, bottom_time) * TIMESTEP; - if (deltad > trial_depth) /* don't test against depth above surface */ - deltad = trial_depth; - add_segment(depth_to_bar(trial_depth, &displayed_dive), - gasmix, - TIMESTEP, po2, &displayed_dive, prefs.decosac); - if (deco_allowed_depth(tissue_tolerance_calc(&displayed_dive, depth_to_bar(trial_depth, &displayed_dive)), - surface_pressure, &displayed_dive, 1) > trial_depth - deltad) { - /* We should have stopped */ - clear_to_ascend = false; - break; - } - trial_depth -= deltad; - } - restore_deco_state(trial_cache); - free(trial_cache); - return clear_to_ascend; -} - -/* Determine if there is enough gas for the dive. Return true if there is enough. - * Also return true if this cannot be calculated because the cylinder doesn't have - * size or a starting pressure. - */ -bool enough_gas(int current_cylinder) -{ - cylinder_t *cyl; - cyl = &displayed_dive.cylinder[current_cylinder]; - - if (!cyl->start.mbar) - return true; - if (cyl->type.size.mliter) - return (float) (cyl->end.mbar - prefs.reserve_gas) * cyl->type.size.mliter / 1000.0 > (float) cyl->deco_gas_used.mliter; - else - return true; -} - -// Work out the stops. Return value is if there were any mandatory stops. - -bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer) -{ - int bottom_depth; - int bottom_gi; - int bottom_stopidx; - bool is_final_plan = true; - int deco_time; - int previous_deco_time; - char *bottom_cache = NULL; - struct sample *sample; - int po2; - int transitiontime, gi; - int current_cylinder; - int stopidx; - int depth; - struct gaschanges *gaschanges = NULL; - int gaschangenr; - int *decostoplevels; - int decostoplevelcount; - int *stoplevels = NULL; - bool stopping = false; - bool pendinggaschange = false; - int clock, previous_point_time; - int avg_depth, max_depth, bottom_time = 0; - int last_ascend_rate; - int best_first_ascend_cylinder; - struct gasmix gas, bottom_gas; - int o2time = 0; - int breaktime = -1; - int breakcylinder = 0; - int error = 0; - bool decodive = false; - - set_gf(diveplan->gflow, diveplan->gfhigh, prefs.gf_low_at_maxdepth); - if (!diveplan->surface_pressure) - diveplan->surface_pressure = SURFACE_PRESSURE; - displayed_dive.surface_pressure.mbar = diveplan->surface_pressure; - clear_deco(displayed_dive.surface_pressure.mbar / 1000.0); - max_bottom_ceiling_pressure.mbar = first_ceiling_pressure.mbar = 0; - create_dive_from_plan(diveplan, is_planner); - - // Do we want deco stop array in metres or feet? - if (prefs.units.length == METERS ) { - decostoplevels = decostoplevels_metric; - decostoplevelcount = sizeof(decostoplevels_metric) / sizeof(int); - } else { - decostoplevels = decostoplevels_imperial; - decostoplevelcount = sizeof(decostoplevels_imperial) / sizeof(int); - } - - /* If the user has selected last stop to be at 6m/20', we need to get rid of the 3m/10' stop. - * Otherwise reinstate the last stop 3m/10' stop. - */ - if (prefs.last_stop) - *(decostoplevels + 1) = 0; - else - *(decostoplevels + 1) = M_OR_FT(3,10); - - /* Let's start at the last 'sample', i.e. the last manually entered waypoint. */ - sample = &displayed_dive.dc.sample[displayed_dive.dc.samples - 1]; - - get_gas_at_time(&displayed_dive, &displayed_dive.dc, sample->time, &gas); - - po2 = sample->setpoint.mbar; - if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) { - report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas)); - current_cylinder = 0; - } - depth = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].depth.mm; - average_max_depth(diveplan, &avg_depth, &max_depth); - last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); - - /* if all we wanted was the dive just get us back to the surface */ - if (!is_planner) { - transitiontime = depth / 75; /* this still needs to be made configurable */ - plan_add_segment(diveplan, transitiontime, 0, gas, po2, false); - create_dive_from_plan(diveplan, is_planner); - return(false); - } - -#if DEBUG_PLAN & 4 - printf("gas %s\n", gasname(&gas)); - printf("depth %5.2lfm \n", depth / 1000.0); -#endif - - best_first_ascend_cylinder = current_cylinder; - /* Find the gases available for deco */ - - if (po2) { // Don't change gas in CCR mode - gaschanges = NULL; - gaschangenr = 0; - } else { - gaschanges = analyze_gaslist(diveplan, &gaschangenr, depth, &best_first_ascend_cylinder); - } - /* Find the first potential decostopdepth above current depth */ - for (stopidx = 0; stopidx < decostoplevelcount; stopidx++) - if (*(decostoplevels + stopidx) >= depth) - break; - if (stopidx > 0) - stopidx--; - /* Stoplevels are either depths of gas changes or potential deco stop depths. */ - stoplevels = sort_stops(decostoplevels, stopidx + 1, gaschanges, gaschangenr); - stopidx += gaschangenr; - - /* Keep time during the ascend */ - bottom_time = clock = previous_point_time = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].time.seconds; - gi = gaschangenr - 1; - - /* Set tissue tolerance and initial vpmb gradient at start of ascent phase */ - tissue_at_end(&displayed_dive, cached_datap); - nuclear_regeneration(clock); - vpmb_start_gradient(); - - if(prefs.deco_mode == RECREATIONAL) { - bool safety_stop = prefs.safetystop && max_depth >= 10000; - track_ascent_gas(depth, &displayed_dive.cylinder[current_cylinder], avg_depth, bottom_time, safety_stop); - // How long can we stay at the current depth and still directly ascent to the surface? - do { - add_segment(depth_to_bar(depth, &displayed_dive), - &displayed_dive.cylinder[current_cylinder].gasmix, - DECOTIMESTEP, po2, &displayed_dive, prefs.bottomsac); - update_cylinder_pressure(&displayed_dive, depth, depth, DECOTIMESTEP, prefs.bottomsac, &displayed_dive.cylinder[current_cylinder], false); - clock += DECOTIMESTEP; - } while (trial_ascent(depth, 0, avg_depth, bottom_time, &displayed_dive.cylinder[current_cylinder].gasmix, - po2, diveplan->surface_pressure / 1000.0) && - enough_gas(current_cylinder)); - - // We did stay one DECOTIMESTEP too many. - // In the best of all worlds, we would roll back also the last add_segment in terms of caching deco state, but - // let's ignore that since for the eventual ascent in recreational mode, nobody looks at the ceiling anymore, - // so we don't really have to compute the deco state. - update_cylinder_pressure(&displayed_dive, depth, depth, -DECOTIMESTEP, prefs.bottomsac, &displayed_dive.cylinder[current_cylinder], false); - clock -= DECOTIMESTEP; - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, true); - previous_point_time = clock; - do { - /* Ascend to surface */ - int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; - if (ascent_velocity(depth, avg_depth, bottom_time) != last_ascend_rate) { - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); - } - if (depth - deltad < 0) - deltad = depth; - - clock += TIMESTEP; - depth -= deltad; - if (depth <= 5000 && depth >= (5000 - deltad) && safety_stop) { - plan_add_segment(diveplan, clock - previous_point_time, 5000, gas, po2, false); - previous_point_time = clock; - clock += 180; - plan_add_segment(diveplan, clock - previous_point_time, 5000, gas, po2, false); - previous_point_time = clock; - safety_stop = false; - } - } while (depth > 0); - plan_add_segment(diveplan, clock - previous_point_time, 0, gas, po2, false); - create_dive_from_plan(diveplan, is_planner); - add_plan_to_notes(diveplan, &displayed_dive, show_disclaimer, error); - fixup_dc_duration(&displayed_dive.dc); - - free(stoplevels); - free(gaschanges); - return(false); - } - - if (best_first_ascend_cylinder != current_cylinder) { - current_cylinder = best_first_ascend_cylinder; - gas = displayed_dive.cylinder[current_cylinder].gasmix; - -#if DEBUG_PLAN & 16 - printf("switch to gas %d (%d/%d) @ %5.2lfm\n", best_first_ascend_cylinder, - (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[best_first_ascend_cylinder].depth / 1000.0); -#endif - } - - // VPM-B or Buehlmann Deco - tissue_at_end(&displayed_dive, cached_datap); - previous_deco_time = 100000000; - deco_time = 10000000; - cache_deco_state(&bottom_cache); // Lets us make several iterations - bottom_depth = depth; - bottom_gi = gi; - bottom_gas = gas; - bottom_stopidx = stopidx; - - //CVA - do { - is_final_plan = (prefs.deco_mode == BUEHLMANN) || (previous_deco_time - deco_time < 10); // CVA time converges - if (deco_time != 10000000) - vpmb_next_gradient(deco_time, diveplan->surface_pressure / 1000.0); - - previous_deco_time = deco_time; - restore_deco_state(bottom_cache); - - depth = bottom_depth; - gi = bottom_gi; - clock = previous_point_time = bottom_time; - gas = bottom_gas; - stopping = false; - decodive = false; - stopidx = bottom_stopidx; - breaktime = -1; - breakcylinder = 0; - o2time = 0; - first_ceiling_pressure.mbar = depth_to_mbar(deco_allowed_depth(tissue_tolerance_calc(&displayed_dive, - depth_to_bar(depth, &displayed_dive)), - diveplan->surface_pressure / 1000.0, - &displayed_dive, - 1), - &displayed_dive); - if (max_bottom_ceiling_pressure.mbar > first_ceiling_pressure.mbar) - first_ceiling_pressure.mbar = max_bottom_ceiling_pressure.mbar; - - last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); - if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) { - report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas)); - current_cylinder = 0; - } - - while (1) { - /* We will break out when we hit the surface */ - do { - /* Ascend to next stop depth */ - int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; - if (ascent_velocity(depth, avg_depth, bottom_time) != last_ascend_rate) { - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - stopping = false; - last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); - } - if (depth - deltad < stoplevels[stopidx]) - deltad = depth - stoplevels[stopidx]; - - add_segment(depth_to_bar(depth, &displayed_dive), - &displayed_dive.cylinder[current_cylinder].gasmix, - TIMESTEP, po2, &displayed_dive, prefs.decosac); - clock += TIMESTEP; - depth -= deltad; - } while (depth > 0 && depth > stoplevels[stopidx]); - - if (depth <= 0) - break; /* We are at the surface */ - - if (gi >= 0 && stoplevels[stopidx] <= gaschanges[gi].depth) { - /* We have reached a gas change. - * Record this in the dive plan */ - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - stopping = true; - - /* Check we need to change cylinder. - * We might not if the cylinder was chosen by the user - * or user has selected only to switch only at required stops. - * If current gas is hypoxic, we want to switch asap */ - - if (current_cylinder != gaschanges[gi].gasidx) { - if (!prefs.switch_at_req_stop || - !trial_ascent(depth, stoplevels[stopidx - 1], avg_depth, bottom_time, - &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0) || get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) < 160) { - current_cylinder = gaschanges[gi].gasidx; - gas = displayed_dive.cylinder[current_cylinder].gasmix; -#if DEBUG_PLAN & 16 - printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi].gasidx, - (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi].depth / 1000.0); -#endif - /* Stop for the minimum duration to switch gas */ - add_segment(depth_to_bar(depth, &displayed_dive), - &displayed_dive.cylinder[current_cylinder].gasmix, - prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac); - clock += prefs.min_switch_duration; - if (prefs.doo2breaks && get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) - o2time += prefs.min_switch_duration; - } else { - /* The user has selected the option to switch gas only at required stops. - * Remember that we are waiting to switch gas - */ - pendinggaschange = true; - } - } - gi--; - } - --stopidx; - - /* Save the current state and try to ascend to the next stopdepth */ - while (1) { - /* Check if ascending to next stop is clear, go back and wait if we hit the ceiling on the way */ - if (trial_ascent(depth, stoplevels[stopidx], avg_depth, bottom_time, - &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0)) - break; /* We did not hit the ceiling */ - - /* Add a minute of deco time and then try again */ - decodive = true; - if (!stopping) { - /* The last segment was an ascend segment. - * Add a waypoint for start of this deco stop */ - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - stopping = true; - } - - /* Are we waiting to switch gas? - * Occurs when the user has selected the option to switch only at required stops - */ - if (pendinggaschange) { - current_cylinder = gaschanges[gi + 1].gasidx; - gas = displayed_dive.cylinder[current_cylinder].gasmix; -#if DEBUG_PLAN & 16 - printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi + 1].gasidx, - (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi + 1].depth / 1000.0); -#endif - /* Stop for the minimum duration to switch gas */ - add_segment(depth_to_bar(depth, &displayed_dive), - &displayed_dive.cylinder[current_cylinder].gasmix, - prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac); - clock += prefs.min_switch_duration; - if (prefs.doo2breaks && get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) - o2time += prefs.min_switch_duration; - pendinggaschange = false; - } - - /* Deco stop should end when runtime is at a whole minute */ - int this_decotimestep; - this_decotimestep = DECOTIMESTEP - clock % DECOTIMESTEP; - - add_segment(depth_to_bar(depth, &displayed_dive), - &displayed_dive.cylinder[current_cylinder].gasmix, - this_decotimestep, po2, &displayed_dive, prefs.decosac); - clock += this_decotimestep; - /* Finish infinite deco */ - if(clock >= 48 * 3600 && depth >= 6000) { - error = LONGDECO; - break; - } - if (prefs.doo2breaks) { - /* The backgas breaks option limits time on oxygen to 12 minutes, followed by 6 minutes on - * backgas (first defined gas). This could be customized if there were demand. - */ - if (get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) { - o2time += DECOTIMESTEP; - if (o2time >= 12 * 60) { - breaktime = 0; - breakcylinder = current_cylinder; - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - current_cylinder = 0; - gas = displayed_dive.cylinder[current_cylinder].gasmix; - } - } else { - if (breaktime >= 0) { - breaktime += DECOTIMESTEP; - if (breaktime >= 6 * 60) { - o2time = 0; - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - current_cylinder = breakcylinder; - gas = displayed_dive.cylinder[current_cylinder].gasmix; - breaktime = -1; - } - } - } - } - } - if (stopping) { - /* Next we will ascend again. Add a waypoint if we have spend deco time */ - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - stopping = false; - } - } - - deco_time = clock - bottom_time; - } while (!is_final_plan); - - plan_add_segment(diveplan, clock - previous_point_time, 0, gas, po2, false); - create_dive_from_plan(diveplan, is_planner); - add_plan_to_notes(diveplan, &displayed_dive, show_disclaimer, error); - fixup_dc_duration(&displayed_dive.dc); - - free(stoplevels); - free(gaschanges); - free(bottom_cache); - return decodive; -} - -/* - * Get a value in tenths (so "10.2" == 102, "9" = 90) - * - * Return negative for errors. - */ -static int get_tenths(const char *begin, const char **endp) -{ - char *end; - int value = strtol(begin, &end, 10); - - if (begin == end) - return -1; - value *= 10; - - /* Fraction? We only look at the first digit */ - if (*end == '.') { - end++; - if (!isdigit(*end)) - return -1; - value += *end - '0'; - do { - end++; - } while (isdigit(*end)); - } - *endp = end; - return value; -} - -static int get_permille(const char *begin, const char **end) -{ - int value = get_tenths(begin, end); - if (value >= 0) { - /* Allow a percentage sign */ - if (**end == '%') - ++*end; - } - return value; -} - -int validate_gas(const char *text, struct gasmix *gas) -{ - int o2, he; - - if (!text) - return 0; - - while (isspace(*text)) - text++; - - if (!*text) - return 0; - - if (!strcasecmp(text, translate("gettextFromC", "air"))) { - o2 = O2_IN_AIR; - he = 0; - text += strlen(translate("gettextFromC", "air")); - } else if (!strcasecmp(text, translate("gettextFromC", "oxygen"))) { - o2 = 1000; - he = 0; - text += strlen(translate("gettextFromC", "oxygen")); - } else if (!strncasecmp(text, translate("gettextFromC", "ean"), 3)) { - o2 = get_permille(text + 3, &text); - he = 0; - } else { - o2 = get_permille(text, &text); - he = 0; - if (*text == '/') - he = get_permille(text + 1, &text); - } - - /* We don't want any extra crud */ - while (isspace(*text)) - text++; - if (*text) - return 0; - - /* Validate the gas mix */ - if (*text || o2 < 1 || o2 > 1000 || he < 0 || o2 + he > 1000) - return 0; - - /* Let it rip */ - gas->o2.permille = o2; - gas->he.permille = he; - return 1; -} - -int validate_po2(const char *text, int *mbar_po2) -{ - int po2; - - if (!text) - return 0; - - po2 = get_tenths(text, &text); - if (po2 < 0) - return 0; - - while (isspace(*text)) - text++; - - while (isspace(*text)) - text++; - if (*text) - return 0; - - *mbar_po2 = po2 * 100; - return 1; -} diff --git a/subsurface-core/planner.h b/subsurface-core/planner.h deleted file mode 100644 index a675989e0..000000000 --- a/subsurface-core/planner.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef PLANNER_H -#define PLANNER_H - -#define LONGDECO 1 -#define NOT_RECREATIONAL 2 - -#ifdef __cplusplus -extern "C" { -#endif - -extern int validate_gas(const char *text, struct gasmix *gas); -extern int validate_po2(const char *text, int *mbar_po2); -extern timestamp_t current_time_notz(void); -extern void set_last_stop(bool last_stop_6m); -extern void set_verbatim(bool verbatim); -extern void set_display_runtime(bool display); -extern void set_display_duration(bool display); -extern void set_display_transitions(bool display); -extern void get_gas_at_time(struct dive *dive, struct divecomputer *dc, duration_t time, struct gasmix *gas); -extern int get_gasidx(struct dive *dive, struct gasmix *mix); -extern bool diveplan_empty(struct diveplan *diveplan); - -extern void free_dps(struct diveplan *diveplan); -extern struct dive *planned_dive; -extern char *cache_data; -extern const char *disclaimer; -extern double plangflow, plangfhigh; - -#ifdef __cplusplus -} -#endif -#endif // PLANNER_H diff --git a/subsurface-core/pluginmanager.cpp b/subsurface-core/pluginmanager.cpp deleted file mode 100644 index 28c978280..000000000 --- a/subsurface-core/pluginmanager.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "pluginmanager.h" - -#include <QApplication> -#include <QDir> -#include <QPluginLoader> -#include <QDebug> - -static QList<ISocialNetworkIntegration*> _socialNetworks; - -PluginManager& PluginManager::instance() -{ - static PluginManager self; - return self; -} - -PluginManager::PluginManager() -{ -} - -void PluginManager::loadPlugins() -{ - QDir pluginsDir(qApp->applicationDirPath()); - -#if defined(Q_OS_WIN) - if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release") - pluginsDir.cdUp(); -#elif defined(Q_OS_MAC) - if (pluginsDir.dirName() == "MacOS") { - pluginsDir.cdUp(); - pluginsDir.cdUp(); - pluginsDir.cdUp(); - } -#endif - pluginsDir.cd("plugins"); - - qDebug() << "Plugins Directory: " << pluginsDir; - foreach (const QString& fileName, pluginsDir.entryList(QDir::Files)) { - QPluginLoader loader(pluginsDir.absoluteFilePath(fileName)); - QObject *plugin = loader.instance(); - if(!plugin) - continue; - - if (ISocialNetworkIntegration *social = qobject_cast<ISocialNetworkIntegration*>(plugin)) { - qDebug() << "Adding the plugin: " << social->socialNetworkName(); - _socialNetworks.push_back(social); - } - } -} - -QList<ISocialNetworkIntegration*> PluginManager::socialNetworkIntegrationPlugins() const -{ - return _socialNetworks; -} diff --git a/subsurface-core/pluginmanager.h b/subsurface-core/pluginmanager.h deleted file mode 100644 index 3f43b5db1..000000000 --- a/subsurface-core/pluginmanager.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef PLUGINMANAGER_H -#define PLUGINMANAGER_H - -#include <QObject> - -#include "isocialnetworkintegration.h" - -class PluginManager { -public: - static PluginManager& instance(); - void loadPlugins(); - QList<ISocialNetworkIntegration*> socialNetworkIntegrationPlugins() const; -private: - PluginManager(); - PluginManager(const PluginManager&); - PluginManager& operator=(const PluginManager&); -}; - -#endif diff --git a/subsurface-core/pref.h b/subsurface-core/pref.h deleted file mode 100644 index be684fd90..000000000 --- a/subsurface-core/pref.h +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef PREF_H -#define PREF_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "units.h" -#include "taxonomy.h" - -/* can't use 'bool' for the boolean values - different size in C and C++ */ -typedef struct -{ - short po2; - short pn2; - short phe; - double po2_threshold; - double pn2_threshold; - double phe_threshold; -} partial_pressure_graphs_t; - -typedef struct { - char *access_token; - char *user_id; - char *album_id; -} facebook_prefs_t; - -typedef struct { - bool enable_geocoding; - bool parse_dive_without_gps; - bool tag_existing_dives; - enum taxonomy_category category[3]; -} geocoding_prefs_t; - -typedef struct { - const char *language; - bool use_system_language; -} locale_prefs_t; - -enum deco_mode { - BUEHLMANN, - RECREATIONAL, - VPMB -}; - -struct preferences { - const char *divelist_font; - const char *default_filename; - const char *default_cylinder; - const char *cloud_base_url; - const char *cloud_git_url; - const char *time_format; - const char *date_format; - const char *date_format_short; - bool time_format_override; - bool date_format_override; - double font_size; - partial_pressure_graphs_t pp_graphs; - short mod; - double modpO2; - short ead; - short dcceiling; - short redceiling; - short calcceiling; - short calcceiling3m; - short calcalltissues; - short calcndltts; - short gflow; - short gfhigh; - int animation_speed; - bool gf_low_at_maxdepth; - bool show_ccr_setpoint; - bool show_ccr_sensors; - short display_invalid_dives; - short unit_system; - struct units units; - bool coordinates_traditional; - short show_sac; - short display_unused_tanks; - short show_average_depth; - short zoomed_plot; - short hrgraph; - short percentagegraph; - short rulergraph; - short tankbar; - short save_userid_local; - char *userid; - int ascrate75; // All rates in mm / sec - int ascrate50; - int ascratestops; - int ascratelast6m; - int descrate; - int bottompo2; - int decopo2; - int proxy_type; - char *proxy_host; - int proxy_port; - short proxy_auth; - char *proxy_user; - char *proxy_pass; - bool doo2breaks; - bool drop_stone_mode; - bool last_stop; // At 6m? - bool verbatim_plan; - bool display_runtime; - bool display_duration; - bool display_transitions; - bool safetystop; - bool switch_at_req_stop; - int reserve_gas; - int min_switch_duration; // seconds - int bottomsac; - int decosac; - int o2consumption; // ml per min - int pscr_ratio; // dump ratio times 1000 - int defaultsetpoint; // default setpoint in mbar - bool show_pictures_in_profile; - bool use_default_file; - short default_file_behavior; - facebook_prefs_t facebook; - char *cloud_storage_password; - char *cloud_storage_newpassword; - char *cloud_storage_email; - char *cloud_storage_email_encoded; - bool save_password_local; - short cloud_verification_status; - bool cloud_background_sync; - geocoding_prefs_t geocoding; - enum deco_mode deco_mode; - short conservatism_level; - int time_threshold; - int distance_threshold; - bool git_local_only; - locale_prefs_t locale; //: TODO: move the rest of locale based info here. -}; -enum unit_system_values { - METRIC, - IMPERIAL, - PERSONALIZE -}; - -enum def_file_behavior { - UNDEFINED_DEFAULT_FILE, - LOCAL_DEFAULT_FILE, - NO_DEFAULT_FILE, - CLOUD_DEFAULT_FILE -}; - -enum cloud_status { - CS_UNKNOWN, - CS_INCORRECT_USER_PASSWD, - CS_NEED_TO_VERIFY, - CS_VERIFIED -}; - -extern struct preferences prefs, default_prefs, informational_prefs; - -#define PP_GRAPHS_ENABLED (prefs.pp_graphs.po2 || prefs.pp_graphs.pn2 || prefs.pp_graphs.phe) - -extern const char *system_divelist_default_font; -extern double system_divelist_default_font_size; - -extern const char *system_default_directory(void); -extern const char *system_default_filename(); -extern bool subsurface_ignore_font(const char *font); -extern void subsurface_OS_pref_setup(); - -#ifdef __cplusplus -} -#endif - -#endif // PREF_H diff --git a/subsurface-core/prefs-macros.h b/subsurface-core/prefs-macros.h deleted file mode 100644 index fe459d3da..000000000 --- a/subsurface-core/prefs-macros.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef PREFSMACROS_H -#define PREFSMACROS_H - -#define SB(V, B) s.setValue(V, (int)(B->isChecked() ? 1 : 0)) - -#define GET_UNIT(name, field, f, t) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.units.field = (v.toInt() == (t)) ? (t) : (f); \ - else \ - prefs.units.field = default_prefs.units.field - -#define GET_BOOL(name, field) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = v.toBool(); \ - else \ - prefs.field = default_prefs.field - -#define GET_DOUBLE(name, field) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = v.toDouble(); \ - else \ - prefs.field = default_prefs.field - -#define GET_INT(name, field) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = v.toInt(); \ - else \ - prefs.field = default_prefs.field - -#define GET_ENUM(name, type, field) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = (enum type)v.toInt(); \ - else \ - prefs.field = default_prefs.field - -#define GET_INT_DEF(name, field, defval) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = v.toInt(); \ - else \ - prefs.field = defval - -#define GET_TXT(name, field) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = strdup(v.toString().toUtf8().constData()); \ - else \ - prefs.field = default_prefs.field - -#define SAVE_OR_REMOVE_SPECIAL(_setting, _default, _compare, _value) \ - if (_compare != _default) \ - s.setValue(_setting, _value); \ - else \ - s.remove(_setting) - -#define SAVE_OR_REMOVE(_setting, _default, _value) \ - if (_value != _default) \ - s.setValue(_setting, _value); \ - else \ - s.remove(_setting) - -#endif // PREFSMACROS_H - diff --git a/subsurface-core/profile.c b/subsurface-core/profile.c deleted file mode 100644 index 6576f6453..000000000 --- a/subsurface-core/profile.c +++ /dev/null @@ -1,1544 +0,0 @@ -/* profile.c */ -/* creates all the necessary data for drawing the dive profile - */ -#include "gettext.h" -#include <limits.h> -#include <string.h> -#include <assert.h> - -#include "dive.h" -#include "display.h" -#include "divelist.h" - -#include "profile.h" -#include "gaspressures.h" -#include "deco.h" -#include "libdivecomputer/parser.h" -#include "libdivecomputer/version.h" -#include "membuffer.h" - -//#define DEBUG_GAS 1 - -#define MAX_PROFILE_DECO 7200 - - -int selected_dive = -1; /* careful: 0 is a valid value */ -unsigned int dc_number = 0; - -static struct plot_data *last_pi_entry_new = NULL; -void populate_pressure_information(struct dive *, struct divecomputer *, struct plot_info *, int); - -extern bool in_planner(); -extern pressure_t first_ceiling_pressure; - -#ifdef DEBUG_PI -/* debugging tool - not normally used */ -static void dump_pi(struct plot_info *pi) -{ - int i; - - printf("pi:{nr:%d maxtime:%d meandepth:%d maxdepth:%d \n" - " maxpressure:%d mintemp:%d maxtemp:%d\n", - pi->nr, pi->maxtime, pi->meandepth, pi->maxdepth, - pi->maxpressure, pi->mintemp, pi->maxtemp); - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = &pi->entry[i]; - printf(" entry[%d]:{cylinderindex:%d sec:%d pressure:{%d,%d}\n" - " time:%d:%02d temperature:%d depth:%d stopdepth:%d stoptime:%d ndl:%d smoothed:%d po2:%lf phe:%lf pn2:%lf sum-pp %lf}\n", - i, entry->cylinderindex, entry->sec, - entry->pressure[0], entry->pressure[1], - entry->sec / 60, entry->sec % 60, - entry->temperature, entry->depth, entry->stopdepth, entry->stoptime, entry->ndl, entry->smoothed, - entry->pressures.o2, entry->pressures.he, entry->pressures.n2, - entry->pressures.o2 + entry->pressures.he + entry->pressures.n2); - } - printf(" }\n"); -} -#endif - -#define ROUND_UP(x, y) ((((x) + (y) - 1) / (y)) * (y)) -#define DIV_UP(x, y) (((x) + (y) - 1) / (y)) - -/* - * When showing dive profiles, we scale things to the - * current dive. However, we don't scale past less than - * 30 minutes or 90 ft, just so that small dives show - * up as such unless zoom is enabled. - * We also need to add 180 seconds at the end so the min/max - * plots correctly - */ -int get_maxtime(struct plot_info *pi) -{ - int seconds = pi->maxtime; - - int DURATION_THR = (pi->dive_type == FREEDIVING ? 60 : 600); - int CEILING = (pi->dive_type == FREEDIVING ? 30 : 60); - - if (prefs.zoomed_plot) { - /* Rounded up to one minute, with at least 2.5 minutes to - * spare. - * For dive times shorter than 10 minutes, we use seconds/4 to - * calculate the space dynamically. - * This is seamless since 600/4 = 150. - */ - if (seconds < DURATION_THR) - return ROUND_UP(seconds + seconds / 4, CEILING); - else - return ROUND_UP(seconds + DURATION_THR/4, CEILING); - } else { -#ifndef SUBSURFACE_MOBILE - /* min 30 minutes, rounded up to 5 minutes, with at least 2.5 minutes to spare */ - return MAX(30 * 60, ROUND_UP(seconds + DURATION_THR/4, CEILING * 5)); -#else - /* just add 2.5 minutes so we have a consistant right margin */ - return seconds + DURATION_THR / 4; -#endif - } -} - -/* get the maximum depth to which we want to plot - * take into account the additional vertical space needed to plot - * partial pressure graphs */ -int get_maxdepth(struct plot_info *pi) -{ - unsigned mm = pi->maxdepth; - int md; - - if (prefs.zoomed_plot) { - /* Rounded up to 10m, with at least 3m to spare */ - md = ROUND_UP(mm + 3000, 10000); - } else { - /* Minimum 30m, rounded up to 10m, with at least 3m to spare */ - md = MAX((unsigned)30000, ROUND_UP(mm + 3000, 10000)); - } - md += pi->maxpp * 9000; - return md; -} - -/* collect all event names and whether we display them */ -struct ev_select *ev_namelist; -int evn_allocated; -int evn_used; - -#if WE_DONT_USE_THIS /* we need to implement event filters in Qt */ -int evn_foreach (void (*callback)(const char *, bool *, void *), void *data) { - int i; - - for (i = 0; i < evn_used; i++) { - /* here we display an event name on screen - so translate */ - callback(translate("gettextFromC", ev_namelist[i].ev_name), &ev_namelist[i].plot_ev, data); - } - return i; -} -#endif /* WE_DONT_USE_THIS */ - -void clear_events(void) -{ - for (int i = 0; i < evn_used; i++) - free(ev_namelist[i].ev_name); - evn_used = 0; -} - -void remember_event(const char *eventname) -{ - int i = 0, len; - - if (!eventname || (len = strlen(eventname)) == 0) - return; - while (i < evn_used) { - if (!strncmp(eventname, ev_namelist[i].ev_name, len)) - return; - i++; - } - if (evn_used == evn_allocated) { - evn_allocated += 10; - ev_namelist = realloc(ev_namelist, evn_allocated * sizeof(struct ev_select)); - if (!ev_namelist) - /* we are screwed, but let's just bail out */ - return; - } - ev_namelist[evn_used].ev_name = strdup(eventname); - ev_namelist[evn_used].plot_ev = true; - evn_used++; -} - -/* UNUSED! */ -static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive) __attribute__((unused)); - -/* Get local sac-rate (in ml/min) between entry1 and entry2 */ -static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive) -{ - int index = entry1->cylinderindex; - cylinder_t *cyl; - int duration = entry2->sec - entry1->sec; - int depth, airuse; - pressure_t a, b; - double atm; - - if (entry2->cylinderindex != index) - return 0; - if (duration <= 0) - return 0; - a.mbar = GET_PRESSURE(entry1); - b.mbar = GET_PRESSURE(entry2); - if (!b.mbar || a.mbar <= b.mbar) - return 0; - - /* Mean pressure in ATM */ - depth = (entry1->depth + entry2->depth) / 2; - atm = depth_to_atm(depth, dive); - - cyl = dive->cylinder + index; - - airuse = gas_volume(cyl, a) - gas_volume(cyl, b); - - /* milliliters per minute */ - return airuse / atm * 60 / duration; -} - -static void analyze_plot_info_minmax_minute(struct plot_data *entry, struct plot_data *first, struct plot_data *last, int index) -{ - struct plot_data *p = entry; - int time = entry->sec; - int seconds = 90 * (index + 1); - struct plot_data *min, *max; - int avg, nr; - - /* Go back 'seconds' in time */ - while (p > first) { - if (p[-1].sec < time - seconds) - break; - p--; - } - - /* Then go forward until we hit an entry past the time */ - min = max = p; - avg = p->depth; - nr = 1; - while (++p < last) { - int depth = p->depth; - if (p->sec > time + seconds) - break; - avg += depth; - nr++; - if (depth < min->depth) - min = p; - if (depth > max->depth) - max = p; - } - entry->min[index] = min; - entry->max[index] = max; - entry->avg[index] = (avg + nr / 2) / nr; -} - -static void analyze_plot_info_minmax(struct plot_data *entry, struct plot_data *first, struct plot_data *last) -{ - analyze_plot_info_minmax_minute(entry, first, last, 0); - analyze_plot_info_minmax_minute(entry, first, last, 1); - analyze_plot_info_minmax_minute(entry, first, last, 2); -} - -static velocity_t velocity(int speed) -{ - velocity_t v; - - if (speed < -304) /* ascent faster than -60ft/min */ - v = CRAZY; - else if (speed < -152) /* above -30ft/min */ - v = FAST; - else if (speed < -76) /* -15ft/min */ - v = MODERATE; - else if (speed < -25) /* -5ft/min */ - v = SLOW; - else if (speed < 25) /* very hard to find data, but it appears that the recommendations - for descent are usually about 2x ascent rate; still, we want - stable to mean stable */ - v = STABLE; - else if (speed < 152) /* between 5 and 30ft/min is considered slow */ - v = SLOW; - else if (speed < 304) /* up to 60ft/min is moderate */ - v = MODERATE; - else if (speed < 507) /* up to 100ft/min is fast */ - v = FAST; - else /* more than that is just crazy - you'll blow your ears out */ - v = CRAZY; - - return v; -} - -struct plot_info *analyze_plot_info(struct plot_info *pi) -{ - int i; - int nr = pi->nr; - - /* Smoothing function: 5-point triangular smooth */ - for (i = 2; i < nr; i++) { - struct plot_data *entry = pi->entry + i; - int depth; - - if (i < nr - 2) { - depth = entry[-2].depth + 2 * entry[-1].depth + 3 * entry[0].depth + 2 * entry[1].depth + entry[2].depth; - entry->smoothed = (depth + 4) / 9; - } - /* vertical velocity in mm/sec */ - /* Linus wants to smooth this - let's at least look at the samples that aren't FAST or CRAZY */ - if (entry[0].sec - entry[-1].sec) { - entry->speed = (entry[0].depth - entry[-1].depth) / (entry[0].sec - entry[-1].sec); - entry->velocity = velocity(entry->speed); - /* if our samples are short and we aren't too FAST*/ - if (entry[0].sec - entry[-1].sec < 15 && entry->velocity < FAST) { - int past = -2; - while (i + past > 0 && entry[0].sec - entry[past].sec < 15) - past--; - entry->velocity = velocity((entry[0].depth - entry[past].depth) / - (entry[0].sec - entry[past].sec)); - } - } else { - entry->velocity = STABLE; - entry->speed = 0; - } - } - - /* One-, two- and three-minute minmax data */ - for (i = 0; i < nr; i++) { - struct plot_data *entry = pi->entry + i; - analyze_plot_info_minmax(entry, pi->entry, pi->entry + nr); - } - - return pi; -} - -/* - * If the event has an explicit cylinder index, - * we return that. If it doesn't, we return the best - * match based on the gasmix. - * - * Some dive computers give cylinder indexes, some - * give just the gas mix. - */ -int get_cylinder_index(struct dive *dive, struct event *ev) -{ - int i; - int best = 0, score = INT_MAX; - int target_o2, target_he; - struct gasmix *g; - - if (ev->gas.index >= 0) - return ev->gas.index; - - g = get_gasmix_from_event(ev); - target_o2 = get_o2(g); - target_he = get_he(g); - - /* - * Try to find a cylinder that best matches the target gas - * mix. - */ - for (i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = dive->cylinder + i; - int delta_o2, delta_he, distance; - - if (cylinder_nodata(cyl)) - continue; - - delta_o2 = get_o2(&cyl->gasmix) - target_o2; - delta_he = get_he(&cyl->gasmix) - target_he; - distance = delta_o2 * delta_o2; - distance += delta_he * delta_he; - - if (distance >= score) - continue; - score = distance; - best = i; - } - return best; -} - -struct event *get_next_event(struct event *event, const char *name) -{ - if (!name || !*name) - return NULL; - while (event) { - if (!strcmp(event->name, name)) - return event; - event = event->next; - } - return event; -} - -static int count_events(struct divecomputer *dc) -{ - int result = 0; - struct event *ev = dc->events; - while (ev != NULL) { - result++; - ev = ev->next; - } - return result; -} - -static int set_cylinder_index(struct plot_info *pi, int i, int cylinderindex, int end) -{ - while (i < pi->nr) { - struct plot_data *entry = pi->entry + i; - if (entry->sec > end) - break; - if (entry->cylinderindex != cylinderindex) { - entry->cylinderindex = cylinderindex; - entry->pressure[0] = 0; - } - i++; - } - return i; -} - -static int set_setpoint(struct plot_info *pi, int i, int setpoint, int end) -{ - while (i < pi->nr) { - struct plot_data *entry = pi->entry + i; - if (entry->sec > end) - break; - entry->o2pressure.mbar = setpoint; - i++; - } - return i; -} - -/* normally the first cylinder has index 0... if not, we need to fix this up here */ -static int set_first_cylinder_index(struct plot_info *pi, int i, int cylinderindex, int end) -{ - while (i < pi->nr) { - struct plot_data *entry = pi->entry + i; - if (entry->sec > end) - break; - entry->cylinderindex = cylinderindex; - i++; - } - return i; -} - -static void check_gas_change_events(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) -{ - int i = 0, cylinderindex = 0; - struct event *ev = get_next_event(dc->events, "gaschange"); - - // for dive computers that tell us their first gas as an event on the first sample - // we need to make sure things are setup correctly - cylinderindex = explicit_first_cylinder(dive, dc); - set_first_cylinder_index(pi, 0, cylinderindex, INT_MAX); - - if (!ev) - return; - - do { - i = set_cylinder_index(pi, i, cylinderindex, ev->time.seconds); - cylinderindex = get_cylinder_index(dive, ev); - ev = get_next_event(ev->next, "gaschange"); - } while (ev); - set_cylinder_index(pi, i, cylinderindex, INT_MAX); -} - -static void check_setpoint_events(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) -{ - int i = 0; - pressure_t setpoint; - (void) dive; - setpoint.mbar = 0; - struct event *ev = get_next_event(dc->events, "SP change"); - - if (!ev) - return; - - do { - i = set_setpoint(pi, i, setpoint.mbar, ev->time.seconds); - setpoint.mbar = ev->value; - if (setpoint.mbar) - dc->divemode = CCR; - ev = get_next_event(ev->next, "SP change"); - } while (ev); - set_setpoint(pi, i, setpoint.mbar, INT_MAX); -} - - -struct plot_info calculate_max_limits_new(struct dive *dive, struct divecomputer *given_dc) -{ - struct divecomputer *dc = &(dive->dc); - bool seen = false; - static struct plot_info pi; - int maxdepth = dive->maxdepth.mm; - unsigned int maxtime = 0; - int maxpressure = 0, minpressure = INT_MAX; - int maxhr = 0, minhr = INT_MAX; - int mintemp = dive->mintemp.mkelvin; - int maxtemp = dive->maxtemp.mkelvin; - int cyl; - - /* Get the per-cylinder maximum pressure if they are manual */ - for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { - int mbar = dive->cylinder[cyl].start.mbar; - if (mbar > maxpressure) - maxpressure = mbar; - if (mbar < minpressure) - minpressure = mbar; - } - - /* Then do all the samples from all the dive computers */ - do { - if (dc == given_dc) - seen = true; - int i = dc->samples; - int lastdepth = 0; - struct sample *s = dc->sample; - - while (--i >= 0) { - int depth = s->depth.mm; - int pressure = s->cylinderpressure.mbar; - int temperature = s->temperature.mkelvin; - int heartbeat = s->heartbeat; - - if (!mintemp && temperature < mintemp) - mintemp = temperature; - if (temperature > maxtemp) - maxtemp = temperature; - - if (pressure && pressure < minpressure) - minpressure = pressure; - if (pressure > maxpressure) - maxpressure = pressure; - if (heartbeat > maxhr) - maxhr = heartbeat; - if (heartbeat < minhr) - minhr = heartbeat; - - if (depth > maxdepth) - maxdepth = s->depth.mm; - if ((depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) && - s->time.seconds > maxtime) - maxtime = s->time.seconds; - lastdepth = depth; - s++; - } - dc = dc->next; - if (dc == NULL && !seen) { - dc = given_dc; - seen = true; - } - } while (dc != NULL); - - if (minpressure > maxpressure) - minpressure = 0; - if (minhr > maxhr) - minhr = 0; - - memset(&pi, 0, sizeof(pi)); - pi.maxdepth = maxdepth; - pi.maxtime = maxtime; - pi.maxpressure = maxpressure; - pi.minpressure = minpressure; - pi.minhr = minhr; - pi.maxhr = maxhr; - pi.mintemp = mintemp; - pi.maxtemp = maxtemp; - return pi; -} - -/* copy the previous entry (we know this exists), update time and depth - * and zero out the sensor pressure (since this is a synthetic entry) - * increment the entry pointer and the count of synthetic entries. */ -#define INSERT_ENTRY(_time, _depth, _sac) \ - *entry = entry[-1]; \ - entry->sec = _time; \ - entry->depth = _depth; \ - entry->running_sum = (entry - 1)->running_sum + (_time - (entry - 1)->sec) * (_depth + (entry - 1)->depth) / 2; \ - SENSOR_PRESSURE(entry) = 0; \ - entry->sac = _sac; \ - entry++; \ - idx++ - -struct plot_data *populate_plot_entries(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) -{ - - int idx, maxtime, nr, i; - int lastdepth, lasttime, lasttemp = 0; - struct plot_data *plot_data; - struct event *ev = dc->events; - (void) dive; - maxtime = pi->maxtime; - - /* - * We want to have a plot_info event at least every 10s (so "maxtime/10+1"), - * but samples could be more dense than that (so add in dc->samples). We also - * need to have one for every event (so count events and add that) and - * additionally we want two surface events around the whole thing (thus the - * additional 4). There is also one extra space for a final entry - * that has time > maxtime (because there can be surface samples - * past "maxtime" in the original sample data) - */ - nr = dc->samples + 6 + maxtime / 10 + count_events(dc); - plot_data = calloc(nr, sizeof(struct plot_data)); - pi->entry = plot_data; - if (!plot_data) - return NULL; - pi->nr = nr; - idx = 2; /* the two extra events at the start */ - - lastdepth = 0; - lasttime = 0; - /* skip events at time = 0 */ - while (ev && ev->time.seconds == 0) - ev = ev->next; - for (i = 0; i < dc->samples; i++) { - struct plot_data *entry = plot_data + idx; - struct sample *sample = dc->sample + i; - int time = sample->time.seconds; - int offset, delta; - int depth = sample->depth.mm; - int sac = sample->sac.mliter; - - /* Add intermediate plot entries if required */ - delta = time - lasttime; - if (delta <= 0) { - time = lasttime; - delta = 1; // avoid divide by 0 - } - for (offset = 10; offset < delta; offset += 10) { - if (lasttime + offset > maxtime) - break; - - /* Add events if they are between plot entries */ - while (ev && (int)ev->time.seconds < lasttime + offset) { - INSERT_ENTRY(ev->time.seconds, interpolate(lastdepth, depth, ev->time.seconds - lasttime, delta), sac); - ev = ev->next; - } - - /* now insert the time interpolated entry */ - INSERT_ENTRY(lasttime + offset, interpolate(lastdepth, depth, offset, delta), sac); - - /* skip events that happened at this time */ - while (ev && (int)ev->time.seconds == lasttime + offset) - ev = ev->next; - } - - /* Add events if they are between plot entries */ - while (ev && (int)ev->time.seconds < time) { - INSERT_ENTRY(ev->time.seconds, interpolate(lastdepth, depth, ev->time.seconds - lasttime, delta), sac); - ev = ev->next; - } - - - entry->sec = time; - entry->depth = depth; - - entry->running_sum = (entry - 1)->running_sum + (time - (entry - 1)->sec) * (depth + (entry - 1)->depth) / 2; - entry->stopdepth = sample->stopdepth.mm; - entry->stoptime = sample->stoptime.seconds; - entry->ndl = sample->ndl.seconds; - entry->tts = sample->tts.seconds; - pi->has_ndl |= sample->ndl.seconds; - entry->in_deco = sample->in_deco; - entry->cns = sample->cns; - if (dc->divemode == CCR) { - entry->o2pressure.mbar = entry->o2setpoint.mbar = sample->setpoint.mbar; // for rebreathers - entry->o2sensor[0].mbar = sample->o2sensor[0].mbar; // for up to three rebreather O2 sensors - entry->o2sensor[1].mbar = sample->o2sensor[1].mbar; - entry->o2sensor[2].mbar = sample->o2sensor[2].mbar; - } else { - entry->pressures.o2 = sample->setpoint.mbar / 1000.0; - } - /* FIXME! sensor index -> cylinder index translation! */ - // entry->cylinderindex = sample->sensor; - SENSOR_PRESSURE(entry) = sample->cylinderpressure.mbar; - O2CYLINDER_PRESSURE(entry) = sample->o2cylinderpressure.mbar; - if (sample->temperature.mkelvin) - entry->temperature = lasttemp = sample->temperature.mkelvin; - else - entry->temperature = lasttemp; - entry->heartbeat = sample->heartbeat; - entry->bearing = sample->bearing.degrees; - entry->sac = sample->sac.mliter; - if (sample->rbt.seconds) - entry->rbt = sample->rbt.seconds; - /* skip events that happened at this time */ - while (ev && (int)ev->time.seconds == time) - ev = ev->next; - lasttime = time; - lastdepth = depth; - idx++; - - if (time > maxtime) - break; - } - - /* Add two final surface events */ - plot_data[idx++].sec = lasttime + 1; - plot_data[idx++].sec = lasttime + 2; - pi->nr = idx; - - return plot_data; -} - -#undef INSERT_ENTRY - -static void populate_cylinder_pressure_data(int idx, int start, int end, struct plot_info *pi, bool o2flag) -{ - int i; - - /* First: check that none of the entries has sensor pressure for this cylinder index */ - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - if (entry->cylinderindex != idx && !o2flag) - continue; - if (CYLINDER_PRESSURE(o2flag, entry)) - return; - } - - /* Then: populate the first entry with the beginning cylinder pressure */ - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - if (entry->cylinderindex != idx && !o2flag) - continue; - if (o2flag) - O2CYLINDER_PRESSURE(entry) = start; - else - SENSOR_PRESSURE(entry) = start; - break; - } - - /* .. and the last entry with the ending cylinder pressure */ - for (i = pi->nr; --i >= 0; /* nothing */) { - struct plot_data *entry = pi->entry + i; - if (entry->cylinderindex != idx && !o2flag) - continue; - if (o2flag) - O2CYLINDER_PRESSURE(entry) = end; - else - SENSOR_PRESSURE(entry) = end; - break; - } -} - -/* - * Calculate the sac rate between the two plot entries 'first' and 'last'. - * - * Everything in between has a cylinder pressure, and it's all the same - * cylinder. - */ -static int sac_between(struct dive *dive, struct plot_data *first, struct plot_data *last) -{ - int airuse; - double pressuretime; - pressure_t a, b; - cylinder_t *cyl; - - if (first == last) - return 0; - - /* Calculate air use - trivial */ - a.mbar = GET_PRESSURE(first); - b.mbar = GET_PRESSURE(last); - cyl = dive->cylinder + first->cylinderindex; - airuse = gas_volume(cyl, a) - gas_volume(cyl, b); - if (airuse <= 0) - return 0; - - /* Calculate depthpressure integrated over time */ - pressuretime = 0.0; - do { - int depth = (first[0].depth + first[1].depth) / 2; - int time = first[1].sec - first[0].sec; - double atm = depth_to_atm(depth, dive); - - pressuretime += atm * time; - } while (++first < last); - - /* Turn "atmseconds" into "atmminutes" */ - pressuretime /= 60; - - /* SAC = mliter per minute */ - return rint(airuse / pressuretime); -} - -/* - * Try to do the momentary sac rate for this entry, averaging over one - * minute. - */ -static void fill_sac(struct dive *dive, struct plot_info *pi, int idx) -{ - struct plot_data *entry = pi->entry + idx; - struct plot_data *first, *last; - int time; - - if (entry->sac) - return; - - if (!GET_PRESSURE(entry)) - return; - - /* - * Try to go back 30 seconds to get 'first'. - * Stop if the sensor changed, or if we went back too far. - */ - first = entry; - time = entry->sec - 30; - while (idx > 0) { - struct plot_data *prev = first-1; - if (prev->cylinderindex != first->cylinderindex) - break; - if (prev->depth < SURFACE_THRESHOLD && first->depth < SURFACE_THRESHOLD) - break; - if (prev->sec < time) - break; - if (!GET_PRESSURE(prev)) - break; - idx--; - first = prev; - } - - /* Now find an entry a minute after the first one */ - last = first; - time = first->sec + 60; - while (++idx < pi->nr) { - struct plot_data *next = last+1; - if (next->cylinderindex != last->cylinderindex) - break; - if (next->depth < SURFACE_THRESHOLD && last->depth < SURFACE_THRESHOLD) - break; - if (next->sec > time) - break; - if (!GET_PRESSURE(next)) - break; - last = next; - } - - /* Ok, now calculate the SAC between 'first' and 'last' */ - entry->sac = sac_between(dive, first, last); -} - -static void calculate_sac(struct dive *dive, struct plot_info *pi) -{ - for (int i = 0; i < pi->nr; i++) - fill_sac(dive, pi, i); -} - -static void populate_secondary_sensor_data(struct divecomputer *dc, struct plot_info *pi) -{ - (void) dc; - (void) pi; - /* We should try to see if it has interesting pressure data here */ -} - -static void setup_gas_sensor_pressure(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) -{ - int i; - struct divecomputer *secondary; - - /* First, populate the pressures with the manual cylinder data.. */ - for (i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = dive->cylinder + i; - int start = cyl->start.mbar ?: cyl->sample_start.mbar; - int end = cyl->end.mbar ?: cyl->sample_end.mbar; - - if (!start || !end) - continue; - - populate_cylinder_pressure_data(i, start, end, pi, dive->cylinder[i].cylinder_use == OXYGEN); - } - - /* - * Here, we should try to walk through all the dive computers, - * and try to see if they have sensor data different from the - * primary dive computer (dc). - */ - secondary = &dive->dc; - do { - if (secondary == dc) - continue; - populate_secondary_sensor_data(dc, pi); - } while ((secondary = secondary->next) != NULL); -} - -#ifndef SUBSURFACE_MOBILE -/* calculate DECO STOP / TTS / NDL */ -static void calculate_ndl_tts(struct plot_data *entry, struct dive *dive, double surface_pressure) -{ - /* FIXME: This should be configurable */ - /* ascent speed up to first deco stop */ - const int ascent_s_per_step = 1; - const int ascent_mm_per_step = 200; /* 12 m/min */ - /* ascent speed between deco stops */ - const int ascent_s_per_deco_step = 1; - const int ascent_mm_per_deco_step = 16; /* 1 m/min */ - /* how long time steps in deco calculations? */ - const int time_stepsize = 60; - const int deco_stepsize = 3000; - /* at what depth is the current deco-step? */ - int next_stop = ROUND_UP(deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), - surface_pressure, dive, 1), deco_stepsize); - int ascent_depth = entry->depth; - /* at what time should we give up and say that we got enuff NDL? */ - int cylinderindex = entry->cylinderindex; - /* If iterating through a dive, entry->tts_calc needs to be reset */ - entry->tts_calc = 0; - - /* If we don't have a ceiling yet, calculate ndl. Don't try to calculate - * a ndl for lower values than 3m it would take forever */ - if (next_stop == 0) { - if (entry->depth < 3000) { - entry->ndl = MAX_PROFILE_DECO; - return; - } - /* stop if the ndl is above max_ndl seconds, and call it plenty of time */ - while (entry->ndl_calc < MAX_PROFILE_DECO && deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), surface_pressure, dive, 1) <= 0) { - entry->ndl_calc += time_stepsize; - add_segment(depth_to_bar(entry->depth, dive), - &dive->cylinder[cylinderindex].gasmix, time_stepsize, entry->o2pressure.mbar, dive, prefs.bottomsac); - } - /* we don't need to calculate anything else */ - return; - } - - /* We are in deco */ - entry->in_deco_calc = true; - - /* Add segments for movement to stopdepth */ - for (; ascent_depth > next_stop; ascent_depth -= ascent_mm_per_step, entry->tts_calc += ascent_s_per_step) { - add_segment(depth_to_bar(ascent_depth, dive), - &dive->cylinder[cylinderindex].gasmix, ascent_s_per_step, entry->o2pressure.mbar, dive, prefs.decosac); - next_stop = ROUND_UP(deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(ascent_depth, dive)), surface_pressure, dive, 1), deco_stepsize); - } - ascent_depth = next_stop; - - /* And how long is the current deco-step? */ - entry->stoptime_calc = 0; - entry->stopdepth_calc = next_stop; - next_stop -= deco_stepsize; - - /* And how long is the total TTS */ - while (next_stop >= 0) { - /* save the time for the first stop to show in the graph */ - if (ascent_depth == entry->stopdepth_calc) - entry->stoptime_calc += time_stepsize; - - entry->tts_calc += time_stepsize; - if (entry->tts_calc > MAX_PROFILE_DECO) - break; - add_segment(depth_to_bar(ascent_depth, dive), - &dive->cylinder[cylinderindex].gasmix, time_stepsize, entry->o2pressure.mbar, dive, prefs.decosac); - - if (deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(ascent_depth,dive)), surface_pressure, dive, 1) <= next_stop) { - /* move to the next stop and add the travel between stops */ - for (; ascent_depth > next_stop; ascent_depth -= ascent_mm_per_deco_step, entry->tts_calc += ascent_s_per_deco_step) - add_segment(depth_to_bar(ascent_depth, dive), - &dive->cylinder[cylinderindex].gasmix, ascent_s_per_deco_step, entry->o2pressure.mbar, dive, prefs.decosac); - ascent_depth = next_stop; - next_stop -= deco_stepsize; - } - } -} - -/* Let's try to do some deco calculations. - */ -void calculate_deco_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool print_mode) -{ - int i, count_iteration = 0; - double surface_pressure = (dc->surface_pressure.mbar ? dc->surface_pressure.mbar : get_surface_pressure_in_mbar(dive, true)) / 1000.0; - int last_ndl_tts_calc_time = 0; - int first_ceiling = 0; - bool first_iteration = true; - int final_tts = 0 , time_clear_ceiling = 0, time_deep_ceiling = 0, deco_time = 0, prev_deco_time = 10000000; - char *cache_data_initial = NULL; - /* For VPM-B outside the planner, cache the initial deco state for CVA iterations */ - if (prefs.deco_mode == VPMB && !in_planner()) - cache_deco_state(&cache_data_initial); - /* For VPM-B outside the planner, iterate until deco time converges (usually one or two iterations after the initial) - * Set maximum number of iterations to 10 just in case */ - while ((abs(prev_deco_time - deco_time) >= 30) && (count_iteration < 10)) { - for (i = 1; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - int j, t0 = (entry - 1)->sec, t1 = entry->sec; - int time_stepsize = 20; - - entry->ambpressure = depth_to_bar(entry->depth, dive); - entry->gfline = MAX((double)prefs.gflow, (entry->ambpressure - surface_pressure) / (gf_low_pressure_this_dive - surface_pressure) * - (prefs.gflow - prefs.gfhigh) + - prefs.gfhigh) * - (100.0 - AMB_PERCENTAGE) / 100.0 + AMB_PERCENTAGE; - if (t0 > t1) { - fprintf(stderr, "non-monotonous dive stamps %d %d\n", t0, t1); - int xchg = t1; - t1 = t0; - t0 = xchg; - } - if (t0 != t1 && t1 - t0 < time_stepsize) - time_stepsize = t1 - t0; - for (j = t0 + time_stepsize; j <= t1; j += time_stepsize) { - int depth = interpolate(entry[-1].depth, entry[0].depth, j - t0, t1 - t0); - add_segment(depth_to_bar(depth, dive), - &dive->cylinder[entry->cylinderindex].gasmix, time_stepsize, entry->o2pressure.mbar, dive, entry->sac); - if ((t1 - j < time_stepsize) && (j < t1)) - time_stepsize = t1 - j; - } - if (t0 == t1) { - entry->ceiling = (entry - 1)->ceiling; - } else { - /* Keep updating the VPM-B gradients until the start of the ascent phase of the dive. */ - if (prefs.deco_mode == VPMB && !in_planner() && (entry - 1)->ceiling >= first_ceiling && first_iteration == true) { - nuclear_regeneration(t1); - vpmb_start_gradient(); - /* For CVA calculations, start by guessing deco time = dive time remaining */ - deco_time = pi->maxtime - t1; - vpmb_next_gradient(deco_time, surface_pressure / 1000.0); - } - entry->ceiling = deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), surface_pressure, dive, !prefs.calcceiling3m); - /* If using VPM-B outside the planner, take first_ceiling_pressure as the deepest ceiling */ - if (prefs.deco_mode == VPMB && !in_planner()) { - if (entry->ceiling >= first_ceiling) { - time_deep_ceiling = t1; - first_ceiling = entry->ceiling; - first_ceiling_pressure.mbar = depth_to_mbar(first_ceiling, dive); - if (first_iteration) { - nuclear_regeneration(t1); - vpmb_start_gradient(); - /* For CVA calculations, start by guessing deco time = dive time remaining */ - deco_time = pi->maxtime - t1; - vpmb_next_gradient(deco_time, surface_pressure / 1000.0); - } - } - // Use the point where the ceiling clears as the end of deco phase for CVA calculations - if (entry->ceiling > 0) - time_clear_ceiling = 0; - else if (time_clear_ceiling == 0) - time_clear_ceiling = t1; - } - } - for (j = 0; j < 16; j++) { - double m_value = buehlmann_inertgas_a[j] + entry->ambpressure / buehlmann_inertgas_b[j]; - entry->ceilings[j] = deco_allowed_depth(tolerated_by_tissue[j], surface_pressure, dive, 1); - entry->percentages[j] = tissue_inertgas_saturation[j] < entry->ambpressure ? - tissue_inertgas_saturation[j] / entry->ambpressure * AMB_PERCENTAGE : - AMB_PERCENTAGE + (tissue_inertgas_saturation[j] - entry->ambpressure) / (m_value - entry->ambpressure) * (100.0 - AMB_PERCENTAGE); - } - - /* should we do more calculations? - * We don't for print-mode because this info doesn't show up there - * If the ceiling hasn't cleared by the last data point, we need tts for VPM-B CVA calculation - * It is not necessary to do these calculation on the first VPMB iteration, except for the last data point */ - if ((prefs.calcndltts && !print_mode && (prefs.deco_mode != VPMB || in_planner() || !first_iteration)) || - (prefs.deco_mode == VPMB && !in_planner() && i == pi->nr - 1)) { - /* only calculate ndl/tts on every 30 seconds */ - if ((entry->sec - last_ndl_tts_calc_time) < 30 && i != pi->nr - 1) { - struct plot_data *prev_entry = (entry - 1); - entry->stoptime_calc = prev_entry->stoptime_calc; - entry->stopdepth_calc = prev_entry->stopdepth_calc; - entry->tts_calc = prev_entry->tts_calc; - entry->ndl_calc = prev_entry->ndl_calc; - continue; - } - last_ndl_tts_calc_time = entry->sec; - - /* We are going to mess up deco state, so store it for later restore */ - char *cache_data = NULL; - cache_deco_state(&cache_data); - calculate_ndl_tts(entry, dive, surface_pressure); - if (prefs.deco_mode == VPMB && !in_planner() && i == pi->nr - 1) - final_tts = entry->tts_calc; - /* Restore "real" deco state for next real time step */ - restore_deco_state(cache_data); - free(cache_data); - } - } - if (prefs.deco_mode == VPMB && !in_planner()) { - prev_deco_time = deco_time; - // Do we need to update deco_time? - if (final_tts > 0) - deco_time = pi->maxtime + final_tts - time_deep_ceiling; - else if (time_clear_ceiling > 0) - deco_time = time_clear_ceiling - time_deep_ceiling; - vpmb_next_gradient(deco_time, surface_pressure / 1000.0); - final_tts = 0; - last_ndl_tts_calc_time = 0; - first_ceiling = 0; - first_iteration = false; - count_iteration ++; - restore_deco_state(cache_data_initial); - } else { - // With Buhlmann, or not in planner, iterating isn't needed. This makes the while condition false. - prev_deco_time = deco_time = 0; - } - } - free(cache_data_initial); -#if DECO_CALC_DEBUG & 1 - dump_tissues(); -#endif -} -#endif - -/* Function calculate_ccr_po2: This function takes information from one plot_data structure (i.e. one point on - * the dive profile), containing the oxygen sensor values of a CCR system and, for that plot_data structure, - * calculates the po2 value from the sensor data. Several rules are applied, depending on how many o2 sensors - * there are and the differences among the readings from these sensors. - */ -static int calculate_ccr_po2(struct plot_data *entry, struct divecomputer *dc) -{ - int sump = 0, minp = 999999, maxp = -999999; - int diff_limit = 100; // The limit beyond which O2 sensor differences are considered significant (default = 100 mbar) - int i, np = 0; - - for (i = 0; i < dc->no_o2sensors; i++) - if (entry->o2sensor[i].mbar) { // Valid reading - ++np; - sump += entry->o2sensor[i].mbar; - minp = MIN(minp, entry->o2sensor[i].mbar); - maxp = MAX(maxp, entry->o2sensor[i].mbar); - } - switch (np) { - case 0: // Uhoh - return entry->o2pressure.mbar; - case 1: // Return what we have - return sump; - case 2: // Take the average - return sump / 2; - case 3: // Voting logic - if (2 * maxp - sump + minp < diff_limit) { // Upper difference acceptable... - if (2 * minp - sump + maxp) // ...and lower difference acceptable - return sump / 3; - else - return (sump - minp) / 2; - } else { - if (2 * minp - sump + maxp) // ...but lower difference acceptable - return (sump - maxp) / 2; - else - return sump / 3; - } - default: // This should not happen - assert(np <= 3); - return 0; - } -} - -static void calculate_gas_information_new(struct dive *dive, struct plot_info *pi) -{ - int i; - double amb_pressure; - - for (i = 1; i < pi->nr; i++) { - int fn2, fhe; - struct plot_data *entry = pi->entry + i; - int cylinderindex = entry->cylinderindex; - - amb_pressure = depth_to_bar(entry->depth, dive); - - fill_pressures(&entry->pressures, amb_pressure, &dive->cylinder[cylinderindex].gasmix, entry->o2pressure.mbar / 1000.0, dive->dc.divemode); - fn2 = (int)(1000.0 * entry->pressures.n2 / amb_pressure); - fhe = (int)(1000.0 * entry->pressures.he / amb_pressure); - - /* Calculate MOD, EAD, END and EADD based on partial pressures calculated before - * so there is no difference in calculating between OC and CC - * END takes Oâ‚‚ + Nâ‚‚ (air) into account ("Narcotic" for trimix dives) - * EAD just uses Nâ‚‚ ("Air" for nitrox dives) */ - pressure_t modpO2 = { .mbar = (int)(prefs.modpO2 * 1000) }; - entry->mod = (double)gas_mod(&dive->cylinder[cylinderindex].gasmix, modpO2, dive, 1).mm; - entry->end = (entry->depth + 10000) * (1000 - fhe) / 1000.0 - 10000; - entry->ead = (entry->depth + 10000) * fn2 / (double)N2_IN_AIR - 10000; - entry->eadd = (entry->depth + 10000) * - (entry->pressures.o2 / amb_pressure * O2_DENSITY + - entry->pressures.n2 / amb_pressure * N2_DENSITY + - entry->pressures.he / amb_pressure * HE_DENSITY) / - (O2_IN_AIR * O2_DENSITY + N2_IN_AIR * N2_DENSITY) * 1000 - 10000; - if (entry->mod < 0) - entry->mod = 0; - if (entry->ead < 0) - entry->ead = 0; - if (entry->end < 0) - entry->end = 0; - if (entry->eadd < 0) - entry->eadd = 0; - } -} - -void fill_o2_values(struct divecomputer *dc, struct plot_info *pi, struct dive *dive) -/* In the samples from each dive computer, there may be uninitialised oxygen - * sensor or setpoint values, e.g. when events were inserted into the dive log - * or if the dive computer does not report o2 values with every sample. But - * for drawing the profile a complete series of valid o2 pressure values is - * required. This function takes the oxygen sensor data and setpoint values - * from the structures of plotinfo and replaces the zero values with their - * last known values so that the oxygen sensor data are complete and ready - * for plotting. This function called by: create_plot_info_new() */ -{ - int i, j; - pressure_t last_sensor[3], o2pressure; - pressure_t amb_pressure; - - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - - if (dc->divemode == CCR) { - if (i == 0) { // For 1st iteration, initialise the last_sensor values - for (j = 0; j < dc->no_o2sensors; j++) - last_sensor[j].mbar = pi->entry->o2sensor[j].mbar; - } else { // Now re-insert the missing oxygen pressure values - for (j = 0; j < dc->no_o2sensors; j++) - if (entry->o2sensor[j].mbar) - last_sensor[j].mbar = entry->o2sensor[j].mbar; - else - entry->o2sensor[j].mbar = last_sensor[j].mbar; - } // having initialised the empty o2 sensor values for this point on the profile, - amb_pressure.mbar = depth_to_mbar(entry->depth, dive); - o2pressure.mbar = calculate_ccr_po2(entry, dc); // ...calculate the po2 based on the sensor data - entry->o2pressure.mbar = MIN(o2pressure.mbar, amb_pressure.mbar); - } else { - entry->o2pressure.mbar = 0; // initialise po2 to zero for dctype = OC - } - } -} - -#ifdef DEBUG_GAS -/* A CCR debug function that writes the cylinder pressure and the oxygen values to the file debug_print_profiledata.dat: - * Called in create_plot_info_new() - */ -static void debug_print_profiledata(struct plot_info *pi) -{ - FILE *f1; - struct plot_data *entry; - int i; - if (!(f1 = fopen("debug_print_profiledata.dat", "w"))) { - printf("File open error for: debug_print_profiledata.dat\n"); - } else { - fprintf(f1, "id t1 gas gasint t2 t3 dil dilint t4 t5 setpoint sensor1 sensor2 sensor3 t6 po2 fo2\n"); - for (i = 0; i < pi->nr; i++) { - entry = pi->entry + i; - fprintf(f1, "%d gas=%8d %8d ; dil=%8d %8d ; o2_sp= %d %d %d %d PO2= %f\n", i, SENSOR_PRESSURE(entry), - INTERPOLATED_PRESSURE(entry), O2CYLINDER_PRESSURE(entry), INTERPOLATED_O2CYLINDER_PRESSURE(entry), - entry->o2pressure.mbar, entry->o2sensor[0].mbar, entry->o2sensor[1].mbar, entry->o2sensor[2].mbar, entry->pressures.o2); - } - fclose(f1); - } -} -#endif - -/* - * Create a plot-info with smoothing and ranged min/max - * - * This also makes sure that we have extra empty events on both - * sides, so that you can do end-points without having to worry - * about it. - */ -void create_plot_info_new(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool fast) -{ - int o2, he, o2max; -#ifndef SUBSURFACE_MOBILE - init_decompression(dive); -#endif - /* Create the new plot data */ - free((void *)last_pi_entry_new); - - get_dive_gas(dive, &o2, &he, &o2max); - if (dc->divemode == FREEDIVE){ - pi->dive_type = FREEDIVE; - } else if (he > 0) { - pi->dive_type = TRIMIX; - } else { - if (o2) - pi->dive_type = NITROX; - else - pi->dive_type = AIR; - } - - last_pi_entry_new = populate_plot_entries(dive, dc, pi); - - check_gas_change_events(dive, dc, pi); /* Populate the gas index from the gas change events */ - check_setpoint_events(dive, dc, pi); /* Populate setpoints */ - setup_gas_sensor_pressure(dive, dc, pi); /* Try to populate our gas pressure knowledge */ - if (!fast) { - populate_pressure_information(dive, dc, pi, false); /* .. calculate missing pressure entries for all gasses except o2 */ - if (dc->divemode == CCR) /* For CCR dives.. */ - populate_pressure_information(dive, dc, pi, true); /* .. calculate missing o2 gas pressure entries */ - } - fill_o2_values(dc, pi, dive); /* .. and insert the O2 sensor data having 0 values. */ - calculate_sac(dive, pi); /* Calculate sac */ -#ifndef SUBSURFACE_MOBILE - calculate_deco_information(dive, dc, pi, false); /* and ceiling information, using gradient factor values in Preferences) */ -#endif - calculate_gas_information_new(dive, pi); /* Calculate gas partial pressures */ - -#ifdef DEBUG_GAS - debug_print_profiledata(pi); -#endif - - pi->meandepth = dive->dc.meandepth.mm; - analyze_plot_info(pi); -} - -struct divecomputer *select_dc(struct dive *dive) -{ - unsigned int max = number_of_computers(dive); - unsigned int i = dc_number; - - /* Reset 'dc_number' if we've switched dives and it is now out of range */ - if (i >= max) - dc_number = i = 0; - - return get_dive_dc(dive, i); -} - -static void plot_string(struct plot_info *pi, struct plot_data *entry, struct membuffer *b, bool has_ndl) -{ - int pressurevalue, mod, ead, end, eadd; - const char *depth_unit, *pressure_unit, *temp_unit, *vertical_speed_unit; - double depthvalue, tempvalue, speedvalue, sacvalue; - int decimals; - const char *unit; - - depthvalue = get_depth_units(entry->depth, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "@: %d:%02d\nD: %.1f%s\n"), FRACTION(entry->sec, 60), depthvalue, depth_unit); - if (GET_PRESSURE(entry)) { - pressurevalue = get_pressure_units(GET_PRESSURE(entry), &pressure_unit); - put_format(b, translate("gettextFromC", "P: %d%s\n"), pressurevalue, pressure_unit); - } - if (entry->temperature) { - tempvalue = get_temp_units(entry->temperature, &temp_unit); - put_format(b, translate("gettextFromC", "T: %.1f%s\n"), tempvalue, temp_unit); - } - speedvalue = get_vertical_speed_units(abs(entry->speed), NULL, &vertical_speed_unit); - /* Ascending speeds are positive, descending are negative */ - if (entry->speed > 0) - speedvalue *= -1; - put_format(b, translate("gettextFromC", "V: %.1f%s\n"), speedvalue, vertical_speed_unit); - sacvalue = get_volume_units(entry->sac, &decimals, &unit); - if (entry->sac && prefs.show_sac) - put_format(b, translate("gettextFromC", "SAC: %.*f%s/min\n"), decimals, sacvalue, unit); - if (entry->cns) - put_format(b, translate("gettextFromC", "CNS: %u%%\n"), entry->cns); - if (prefs.pp_graphs.po2) - put_format(b, translate("gettextFromC", "pO%s: %.2fbar\n"), UTF8_SUBSCRIPT_2, entry->pressures.o2); - if (prefs.pp_graphs.pn2) - put_format(b, translate("gettextFromC", "pN%s: %.2fbar\n"), UTF8_SUBSCRIPT_2, entry->pressures.n2); - if (prefs.pp_graphs.phe) - put_format(b, translate("gettextFromC", "pHe: %.2fbar\n"), entry->pressures.he); - if (prefs.mod) { - mod = (int)get_depth_units(entry->mod, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "MOD: %d%s\n"), mod, depth_unit); - } - eadd = (int)get_depth_units(entry->eadd, NULL, &depth_unit); - if (prefs.ead) { - switch (pi->dive_type) { - case NITROX: - ead = (int)get_depth_units(entry->ead, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "EAD: %d%s\nEADD: %d%s\n"), ead, depth_unit, eadd, depth_unit); - break; - case TRIMIX: - end = (int)get_depth_units(entry->end, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "END: %d%s\nEADD: %d%s\n"), end, depth_unit, eadd, depth_unit); - break; - case AIR: - case FREEDIVING: - /* nothing */ - break; - } - } - if (entry->stopdepth) { - depthvalue = get_depth_units(entry->stopdepth, NULL, &depth_unit); - if (entry->ndl) { - /* this is a safety stop as we still have ndl */ - if (entry->stoptime) - put_format(b, translate("gettextFromC", "Safetystop: %umin @ %.0f%s\n"), DIV_UP(entry->stoptime, 60), - depthvalue, depth_unit); - else - put_format(b, translate("gettextFromC", "Safetystop: unkn time @ %.0f%s\n"), - depthvalue, depth_unit); - } else { - /* actual deco stop */ - if (entry->stoptime) - put_format(b, translate("gettextFromC", "Deco: %umin @ %.0f%s\n"), DIV_UP(entry->stoptime, 60), - depthvalue, depth_unit); - else - put_format(b, translate("gettextFromC", "Deco: unkn time @ %.0f%s\n"), - depthvalue, depth_unit); - } - } else if (entry->in_deco) { - put_string(b, translate("gettextFromC", "In deco\n")); - } else if (has_ndl) { - put_format(b, translate("gettextFromC", "NDL: %umin\n"), DIV_UP(entry->ndl, 60)); - } - if (entry->tts) - put_format(b, translate("gettextFromC", "TTS: %umin\n"), DIV_UP(entry->tts, 60)); - if (entry->stopdepth_calc && entry->stoptime_calc) { - depthvalue = get_depth_units(entry->stopdepth_calc, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "Deco: %umin @ %.0f%s (calc)\n"), DIV_UP(entry->stoptime_calc, 60), - depthvalue, depth_unit); - } else if (entry->in_deco_calc) { - /* This means that we have no NDL left, - * and we have no deco stop, - * so if we just accend to the surface slowly - * (ascent_mm_per_step / ascent_s_per_step) - * everything will be ok. */ - put_string(b, translate("gettextFromC", "In deco (calc)\n")); - } else if (prefs.calcndltts && entry->ndl_calc != 0) { - if(entry->ndl_calc < MAX_PROFILE_DECO) - put_format(b, translate("gettextFromC", "NDL: %umin (calc)\n"), DIV_UP(entry->ndl_calc, 60)); - else - put_format(b, "%s", translate("gettextFromC", "NDL: >2h (calc)\n")); - } - if (entry->tts_calc) { - if (entry->tts_calc < MAX_PROFILE_DECO) - put_format(b, translate("gettextFromC", "TTS: %umin (calc)\n"), DIV_UP(entry->tts_calc, 60)); - else - put_format(b, "%s", translate("gettextFromC", "TTS: >2h (calc)\n")); - } - if (entry->rbt) - put_format(b, translate("gettextFromC", "RBT: %umin\n"), DIV_UP(entry->rbt, 60)); - if (entry->ceiling) { - depthvalue = get_depth_units(entry->ceiling, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "Calculated ceiling %.0f%s\n"), depthvalue, depth_unit); - if (prefs.calcalltissues) { - int k; - for (k = 0; k < 16; k++) { - if (entry->ceilings[k]) { - depthvalue = get_depth_units(entry->ceilings[k], NULL, &depth_unit); - put_format(b, translate("gettextFromC", "Tissue %.0fmin: %.1f%s\n"), buehlmann_N2_t_halflife[k], depthvalue, depth_unit); - } - } - } - } - if (entry->heartbeat && prefs.hrgraph) - put_format(b, translate("gettextFromC", "heartbeat: %d\n"), entry->heartbeat); - if (entry->bearing) - put_format(b, translate("gettextFromC", "bearing: %d\n"), entry->bearing); - if (entry->running_sum) { - depthvalue = get_depth_units(entry->running_sum / entry->sec, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "mean depth to here %.1f%s\n"), depthvalue, depth_unit); - } - - strip_mb(b); -} - -struct plot_data *get_plot_details_new(struct plot_info *pi, int time, struct membuffer *mb) -{ - struct plot_data *entry = NULL; - int i; - - for (i = 0; i < pi->nr; i++) { - entry = pi->entry + i; - if (entry->sec >= time) - break; - } - if (entry) - plot_string(pi, entry, mb, pi->has_ndl); - return (entry); -} - -/* Compare two plot_data entries and writes the results into a string */ -void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int bufsize, int sum) -{ - struct plot_data *start, *stop, *data; - const char *depth_unit, *pressure_unit, *vertical_speed_unit; - char *buf2 = malloc(bufsize); - int avg_speed, max_asc_speed, max_desc_speed; - int delta_depth, avg_depth, max_depth, min_depth; - int bar_used, last_pressure, pressurevalue; - int count, last_sec, delta_time; - - double depthvalue, speedvalue; - - if (bufsize > 0) - buf[0] = '\0'; - if (e1 == NULL || e2 == NULL) { - free(buf2); - return; - } - - if (e1->sec < e2->sec) { - start = e1; - stop = e2; - } else if (e1->sec > e2->sec) { - start = e2; - stop = e1; - } else { - free(buf2); - return; - } - count = 0; - avg_speed = 0; - max_asc_speed = 0; - max_desc_speed = 0; - - delta_depth = abs(start->depth - stop->depth); - delta_time = abs(start->sec - stop->sec); - avg_depth = 0; - max_depth = 0; - min_depth = INT_MAX; - bar_used = 0; - - last_sec = start->sec; - last_pressure = GET_PRESSURE(start); - - data = start; - while (data != stop) { - data = start + count; - if (sum) - avg_speed += abs(data->speed) * (data->sec - last_sec); - else - avg_speed += data->speed * (data->sec - last_sec); - avg_depth += data->depth * (data->sec - last_sec); - - if (data->speed > max_desc_speed) - max_desc_speed = data->speed; - if (data->speed < max_asc_speed) - max_asc_speed = data->speed; - - if (data->depth < min_depth) - min_depth = data->depth; - if (data->depth > max_depth) - max_depth = data->depth; - /* Try to detect gas changes */ - if (GET_PRESSURE(data) < last_pressure + 2000) - bar_used += last_pressure - GET_PRESSURE(data); - - count += 1; - last_sec = data->sec; - last_pressure = GET_PRESSURE(data); - } - avg_depth /= stop->sec - start->sec; - avg_speed /= stop->sec - start->sec; - - snprintf(buf, bufsize, translate("gettextFromC", "%sT: %d:%02d min"), UTF8_DELTA, delta_time / 60, delta_time % 60); - memcpy(buf2, buf, bufsize); - - depthvalue = get_depth_units(delta_depth, NULL, &depth_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s"), buf2, UTF8_DELTA, depthvalue, depth_unit); - memcpy(buf2, buf, bufsize); - - depthvalue = get_depth_units(min_depth, NULL, &depth_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s"), buf2, UTF8_DOWNWARDS_ARROW, depthvalue, depth_unit); - memcpy(buf2, buf, bufsize); - - depthvalue = get_depth_units(max_depth, NULL, &depth_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s"), buf2, UTF8_UPWARDS_ARROW, depthvalue, depth_unit); - memcpy(buf2, buf, bufsize); - - depthvalue = get_depth_units(avg_depth, NULL, &depth_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s\n"), buf2, UTF8_AVERAGE, depthvalue, depth_unit); - memcpy(buf2, buf, bufsize); - - speedvalue = get_vertical_speed_units(abs(max_desc_speed), NULL, &vertical_speed_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s%sV:%.2f%s"), buf2, UTF8_DOWNWARDS_ARROW, speedvalue, vertical_speed_unit); - memcpy(buf2, buf, bufsize); - - speedvalue = get_vertical_speed_units(abs(max_asc_speed), NULL, &vertical_speed_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sV:%.2f%s"), buf2, UTF8_UPWARDS_ARROW, speedvalue, vertical_speed_unit); - memcpy(buf2, buf, bufsize); - - speedvalue = get_vertical_speed_units(abs(avg_speed), NULL, &vertical_speed_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sV:%.2f%s"), buf2, UTF8_AVERAGE, speedvalue, vertical_speed_unit); - memcpy(buf2, buf, bufsize); - - /* Only print if gas has been used */ - if (bar_used) { - pressurevalue = get_pressure_units(bar_used, &pressure_unit); - memcpy(buf2, buf, bufsize); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sP:%d %s"), buf2, UTF8_DELTA, pressurevalue, pressure_unit); - } - - free(buf2); -} diff --git a/subsurface-core/profile.h b/subsurface-core/profile.h deleted file mode 100644 index abac9dd49..000000000 --- a/subsurface-core/profile.h +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef PROFILE_H -#define PROFILE_H - -#include "dive.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - STABLE, - SLOW, - MODERATE, - FAST, - CRAZY -} velocity_t; - -struct membuffer; -struct divecomputer; -struct plot_info; -struct plot_data { - unsigned int in_deco : 1; - int cylinderindex; - int sec; - /* pressure[0] is sensor cylinder pressure [when CCR, the pressure of the diluent cylinder] - * pressure[1] is interpolated cylinder pressure */ - int pressure[2]; - /* o2pressure[0] is o2 cylinder pressure [CCR] - * o2pressure[1] is interpolated o2 cylinder pressure [CCR] */ - int o2cylinderpressure[2]; - int temperature; - /* Depth info */ - int depth; - int ceiling; - int ceilings[16]; - int percentages[16]; - int ndl; - int tts; - int rbt; - int stoptime; - int stopdepth; - int cns; - int smoothed; - int sac; - int running_sum; - struct gas_pressures pressures; - pressure_t o2pressure; // for rebreathers, this is consensus measured po2, or setpoint otherwise. 0 for OC. - pressure_t o2sensor[3]; //for rebreathers with up to 3 PO2 sensors - pressure_t o2setpoint; - double mod, ead, end, eadd; - velocity_t velocity; - int speed; - struct plot_data *min[3]; - struct plot_data *max[3]; - int avg[3]; - /* values calculated by us */ - unsigned int in_deco_calc : 1; - int ndl_calc; - int tts_calc; - int stoptime_calc; - int stopdepth_calc; - int pressure_time; - int heartbeat; - int bearing; - double ambpressure; - double gfline; -}; - -struct ev_select { - char *ev_name; - bool plot_ev; -}; - -struct plot_info calculate_max_limits_new(struct dive *dive, struct divecomputer *given_dc); -void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int bufsize, int sum); -struct plot_data *populate_plot_entries(struct dive *dive, struct divecomputer *dc, struct plot_info *pi); -struct plot_info *analyze_plot_info(struct plot_info *pi); -void create_plot_info_new(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool fast); -void calculate_deco_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool print_mode); -struct plot_data *get_plot_details_new(struct plot_info *pi, int time, struct membuffer *); - -/* - * When showing dive profiles, we scale things to the - * current dive. However, we don't scale past less than - * 30 minutes or 90 ft, just so that small dives show - * up as such unless zoom is enabled. - * We also need to add 180 seconds at the end so the min/max - * plots correctly - */ -int get_maxtime(struct plot_info *pi); - -/* get the maximum depth to which we want to plot - * take into account the additional verical space needed to plot - * partial pressure graphs */ -int get_maxdepth(struct plot_info *pi); - -#define SENSOR_PR 0 -#define INTERPOLATED_PR 1 -#define SENSOR_PRESSURE(_entry) (_entry)->pressure[SENSOR_PR] -#define O2CYLINDER_PRESSURE(_entry) (_entry)->o2cylinderpressure[SENSOR_PR] -#define CYLINDER_PRESSURE(_o2, _entry) (_o2 ? O2CYLINDER_PRESSURE(_entry) : SENSOR_PRESSURE(_entry)) -#define INTERPOLATED_PRESSURE(_entry) (_entry)->pressure[INTERPOLATED_PR] -#define INTERPOLATED_O2CYLINDER_PRESSURE(_entry) (_entry)->o2cylinderpressure[INTERPOLATED_PR] -#define GET_PRESSURE(_entry) (SENSOR_PRESSURE(_entry) ? SENSOR_PRESSURE(_entry) : INTERPOLATED_PRESSURE(_entry)) -#define GET_O2CYLINDER_PRESSURE(_entry) (O2CYLINDER_PRESSURE(_entry) ? O2CYLINDER_PRESSURE(_entry) : INTERPOLATED_O2CYLINDER_PRESSURE(_entry)) -#define SAC_WINDOW 45 /* sliding window in seconds for current SAC calculation */ - -#ifdef __cplusplus -} -#endif -#endif // PROFILE_H diff --git a/subsurface-core/qt-gui.h b/subsurface-core/qt-gui.h deleted file mode 100644 index 92532d22f..000000000 --- a/subsurface-core/qt-gui.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef QT_GUI_H -#define QT_GUI_H - -void init_qt_late(); -void init_ui(); - -void run_ui(); -void exit_ui(); - -#if defined(SUBSURFACE_MOBILE) -#include <QQuickWindow> -extern QObject *qqWindowObject; -#endif - -#endif // QT_GUI_H diff --git a/subsurface-core/qt-init.cpp b/subsurface-core/qt-init.cpp deleted file mode 100644 index b52dfd970..000000000 --- a/subsurface-core/qt-init.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include <QApplication> -#include <QNetworkProxy> -#include <QLibraryInfo> -#include <QTextCodec> -#include "helpers.h" - -void init_qt_late() -{ - QApplication *application = qApp; - // tell Qt to use system proxies - // note: on Linux, "system" == "environment variables" - QNetworkProxyFactory::setUseSystemConfiguration(true); - - // for Win32 and Qt5 we try to set the locale codec to UTF-8. - // this makes QFile::encodeName() work. -#ifdef Q_OS_WIN - QTextCodec::setCodecForLocale(QTextCodec::codecForMib(106)); -#endif - - QCoreApplication::setOrganizationName("Subsurface"); - QCoreApplication::setOrganizationDomain("subsurface.hohndel.org"); - QCoreApplication::setApplicationName("Subsurface"); - // find plugins installed in the application directory (without this SVGs don't work on Windows) - QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); - QLocale loc; - QString uiLang = uiLanguage(&loc); - QLocale::setDefault(loc); - - // we don't have translations for English - if we don't check for this - // Qt will proceed to load the second language in preference order - not what we want - // on Linux this tends to be en-US, but on the Mac it's just en - if (!uiLang.startsWith("en") || uiLang.startsWith("en-GB")) { - qtTranslator = new QTranslator; - if (qtTranslator->load(loc, "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { - application->installTranslator(qtTranslator); - } else { - qDebug() << "can't find Qt localization for locale" << uiLang << "searching in" << QLibraryInfo::location(QLibraryInfo::TranslationsPath); - } - ssrfTranslator = new QTranslator; - if (ssrfTranslator->load(loc, "subsurface", "_") || - ssrfTranslator->load(loc, "subsurface", "_", getSubsurfaceDataPath("translations")) || - ssrfTranslator->load(loc, "subsurface", "_", getSubsurfaceDataPath("../translations"))) { - application->installTranslator(ssrfTranslator); - } else { - qDebug() << "can't find Subsurface localization for locale" << uiLang; - } - } -} diff --git a/subsurface-core/qthelper.cpp b/subsurface-core/qthelper.cpp deleted file mode 100644 index c45e86388..000000000 --- a/subsurface-core/qthelper.cpp +++ /dev/null @@ -1,1615 +0,0 @@ -#include "qthelper.h" -#include "helpers.h" -#include "gettextfromc.h" -#include "statistics.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 "prefs-macros.h" -#include <QFile> -#include <QRegExp> -#include <QDir> -#include <QDebug> -#include <QSettings> -#include <QStandardPaths> -#include <QJsonDocument> -#include <QNetworkReply> -#include <QNetworkRequest> -#include <QNetworkAccessManager> -#include <QNetworkProxy> -#include <QDateTime> -#include <QImageReader> -#include <QtConcurrent> -#include <QFont> -#include <QApplication> -#include <QTextDocument> - -#include <libxslt/documents.h> - -const char *existing_filename; -static QLocale loc; - -#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("%1").arg(kg + (gr >= 500 ? 1 : 0)); - else - str = QString("%1.%2").arg(kg).arg((unsigned)(gr + 50) / 100); - } else { - double lbs = grams_to_lbs(weight_in_grams); - if (lbs >= 40.0) - lbs = rint(lbs + 0.5); - else - lbs = rint(lbs + 0.05); - str = QString("%1").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1); - } - return (str); -} - -QString distance_string(int distanceInMeters) -{ - QString str; - if(get_units()->length == units::METERS) { - if (distanceInMeters >= 1000) - str = QString(translate("gettextFromC", "%1km")).arg(distanceInMeters / 1000); - else - str = QString(translate("gettextFromC", "%1m")).arg(distanceInMeters); - } else { - double miles = m_to_mile(distanceInMeters); - if (miles >= 1.0) - str = QString(translate("gettextFromC", "%1mi")).arg((int)miles); - else - str = QString(translate("gettextFromC", "%1yd")).arg((int)(miles * 1760)); - } - 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(""); - - if (prefs.coordinates_traditional) { - 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()); - } else { - result.sprintf("%f %f", (double) lat / 1000000.0, (double) lon / 1000000.0); - } - 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 if ((numberDefined || minutesDefined || secondsDefined) && - (txt[pos] == ',' || txt[pos] == ';')) { - // next coordinate coming up - // eat the ',' and any subsequent white space - while (txt[++pos].isSpace()) - /* nothing */ ; - break; - } 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)) { - if (verbose > 0) { - qDebug() << "cannot open stylesheet" << QLatin1String(":/xslt/") + (const char *)uri; - 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" timestamp_t picture_get_timestamp(char *filename) -{ - EXIFInfo exif; - memblock mem; - int retval; - - // filename might not be the actual filename, so let's go via the hash. - if (readfile(localFilePath(QString(filename)).toUtf8().data(), &mem) <= 0) - return 0; - retval = exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size); - free(mem.buffer); - if (retval != PARSE_EXIF_SUCCESS) - return 0; - return exif.epoch(); -} - -extern "C" char *move_away(const char *old_path) -{ - if (verbose > 1) - qDebug() << "move away" << old_path; - QDir oldDir(old_path); - QDir newDir; - QString newPath; - int i = 0; - do { - newPath = QString(old_path) + QString(".%1").arg(++i); - newDir.setPath(newPath); - } while(newDir.exists()); - if (verbose > 1) - qDebug() << "renaming to" << newPath; - if (!oldDir.rename(old_path, newPath)) { - if (verbose) - qDebug() << "rename of" << old_path << "to" << newPath << "failed"; - // this next one we only try on Windows... if we are on a different platform - // we simply give up and return an empty string -#ifdef WIN32 - if (subsurface_dir_rename(old_path, qPrintable(newPath)) == 0) -#endif - return strdup(""); - } - return strdup(qPrintable(newPath)); -} - -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 *path, const char *cnewName) -{ - QString fileName(cfileName); - QString newName(path); - newName += cnewName; - QFile file(newName); - if (file.exists()) - file.remove(); - if (!QFile::copy(fileName, newName)) - qDebug() << "copy of" << fileName << "to" << newName << "failed"; -} - -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 -#ifdef SUBSURFACE_MOBILE - QString userAgent = QString("Subsurface-mobile:%1(%2):").arg(subsurface_mobile_version()).arg(subsurface_canonical_version()); -#else - QString userAgent = QString("Subsurface:%1:").arg(subsurface_canonical_version()); -#endif - 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; - -} - -extern "C" const char *subsurface_user_agent() -{ - static QString uA = getUserAgent(); - - return strdup(qPrintable(uA)); -} - -QString uiLanguage(QLocale *callerLoc) -{ - QString shortDateFormat; - QString dateFormat; - QString timeFormat; - QSettings s; - QVariant v; - s.beginGroup("Language"); - - if (!s.value("UseSystemLanguage", true).toBool()) { - loc = QLocale(s.value("UiLanguage", QLocale().uiLanguages().first()).toString()); - } else { - loc = QLocale(QLocale().uiLanguages().first()); - } - QStringList languages = loc.uiLanguages(); - QString uiLang; - if (languages[0].contains('-')) - uiLang = languages[0]; - else if (languages.count() > 1 && languages[1].contains('-')) - uiLang = languages[1]; - else if (languages.count() > 2 && languages[2].contains('-')) - uiLang = languages[2]; - else - uiLang = languages[0]; - GET_BOOL("time_format_override", time_format_override); - GET_BOOL("date_format_override", date_format_override); - GET_TXT("time_format", time_format); - GET_TXT("date_format", date_format); - GET_TXT("date_format_short", date_format_short); - 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; - QStringList languages = loc2.uiLanguages(); - if (languages[0].contains('-')) - uiLang = languages[0]; - else if (languages.count() > 1 && languages[1].contains('-')) - uiLang = languages[1]; - else if (languages.count() > 2 && languages[2].contains('-')) - uiLang = languages[2]; - } - if (callerLoc) - *callerLoc = loc; - - if (!prefs.date_format_override || same_string(prefs.date_format_short, "") || same_string(prefs.date_format, "")) { - // derive our standard date format from what the locale gives us - // 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"); - if (!prefs.date_format_override || same_string(prefs.date_format, "")) { - free((void*)prefs.date_format); - prefs.date_format = strdup(qPrintable(dateFormat)); - } - if (!prefs.date_format_override || same_string(prefs.date_format_short, "")) { - free((void*)prefs.date_format_short); - prefs.date_format_short = strdup(qPrintable(shortDateFormat)); - } - } - if (!prefs.time_format_override || same_string(prefs.time_format, "")) { - 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", ""); - free((void*)prefs.time_format); - prefs.time_format = strdup(qPrintable(timeFormat)); - } - return uiLang; -} - -QLocale getLocale() -{ - return loc; -} - -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) -{ - const char *unit; - int decimals; - double value = get_volume_units(volume.mliter, &decimals, &unit); - 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(""); -} - -static const char *printing_templates = "printing_templates"; - -QString getPrintingTemplatePathUser() -{ - static QString path = QString(); - if (path.isEmpty()) - path = QString(system_default_directory()) + QDir::separator() + QString(printing_templates); - return path; -} - -QString getPrintingTemplatePathBundle() -{ - static QString path = QString(); - if (path.isEmpty()) - path = getSubsurfaceDataPath(printing_templates); - return path; -} - -void copyPath(QString src, QString dst) -{ - QDir dir(src); - if (!dir.exists()) - return; - foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { - QString dst_path = dst + QDir::separator() + d; - dir.mkpath(dst_path); - copyPath(src + QDir::separator() + d, dst_path); - } - foreach (QString f, dir.entryList(QDir::Files)) - QFile::copy(src + QDir::separator() + f, dst + QDir::separator() + f); -} - -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 parseLengthToMm(const QString &text) -{ - int mm; - QString numOnly = text; - numOnly.replace(",", ".").remove(QRegExp("[^-0-9.]")); - if (numOnly.isEmpty()) - return 0; - double number = numOnly.toDouble(); - if (text.contains(QObject::tr("m"), Qt::CaseInsensitive)) { - mm = number * 1000; - } else if (text.contains(QObject::tr("ft"), Qt::CaseInsensitive)) { - mm = feet_to_mm(number); - } else { - switch (prefs.units.length) { - case units::FEET: - mm = feet_to_mm(number); - break; - case units::METERS: - mm = number * 1000; - break; - default: - mm = 0; - } - } - return mm; - -} - -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(); - if (text.contains(QObject::tr("C"), Qt::CaseInsensitive)) { - mkelvin = C_to_mkelvin(number); - } else if (text.contains(QObject::tr("F"), Qt::CaseInsensitive)) { - mkelvin = F_to_mkelvin(number); - } else { - 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; -} - -int parseWeightToGrams(const QString &text) -{ - int grams; - QString numOnly = text; - numOnly.replace(",", ".").remove(QRegExp("[^0-9.]")); - if (numOnly.isEmpty()) - return 0; - double number = numOnly.toDouble(); - if (text.contains(QObject::tr("kg"), Qt::CaseInsensitive)) { - grams = rint(number * 1000); - } else if (text.contains(QObject::tr("lbs"), Qt::CaseInsensitive)) { - grams = lbs_to_grams(number); - } else { - switch (prefs.units.weight) { - case units::KG: - grams = rint(number * 1000); - break; - case units::LBS: - grams = lbs_to_grams(number); - break; - default: - grams = 0; - } - } - return grams; -} - -int parsePressureToMbar(const QString &text) -{ - int mbar; - QString numOnly = text; - numOnly.replace(",", ".").remove(QRegExp("[^0-9.]")); - if (numOnly.isEmpty()) - return 0; - double number = numOnly.toDouble(); - if (text.contains(QObject::tr("bar"), Qt::CaseInsensitive)) { - mbar = rint(number * 1000); - } else if (text.contains(QObject::tr("psi"), Qt::CaseInsensitive)) { - mbar = psi_to_mbar(number); - } else { - switch (prefs.units.pressure) { - case units::BAR: - mbar = rint(number * 1000); - break; - case units::PSI: - mbar = psi_to_mbar(number); - break; - default: - mbar = 0; - } - } - return mbar; -} - -int parseGasMixO2(const QString &text) -{ - QString gasString = text; - int o2, number; - if (gasString.contains(QObject::tr("AIR"), Qt::CaseInsensitive)) { - o2 = O2_IN_AIR; - } else if (gasString.contains(QObject::tr("EAN"), Qt::CaseInsensitive)) { - gasString.remove(QRegExp("[^0-9]")); - number = gasString.toInt(); - o2 = number * 10; - } else if (gasString.contains("/")) { - QStringList gasSplit = gasString.split("/"); - number = gasSplit[0].toInt(); - o2 = number * 10; - } else { - number = gasString.toInt(); - o2 = number * 10; - } - return o2; -} - -int parseGasMixHE(const QString &text) -{ - QString gasString = text; - int he, number; - if (gasString.contains("/")) { - QStringList gasSplit = gasString.split("/"); - number = gasSplit[1].toInt(); - he = number * 10; - } else { - he = 0; - } - return he; -} - -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(), QString(prefs.date_format) + " " + prefs.time_format); -} - -QString get_short_dive_date_string(timestamp_t when) -{ - QDateTime ts; - ts.setMSecsSinceEpoch(when * 1000L); - return loc.toString(ts.toUTC(), QString(prefs.date_format_short) + " " + prefs.time_format); -} - -const char *get_dive_date_c_string(timestamp_t when) -{ - QString text = get_dive_date_string(when); - return strdup(text.toUtf8().data()); -} - -bool is_same_day(timestamp_t trip_when, timestamp_t dive_when) -{ - static timestamp_t twhen = (timestamp_t) 0; - static struct tm tmt; - struct tm tmd; - - utc_mkdate(dive_when, &tmd); - - if (twhen != trip_when) { - twhen = trip_when; - utc_mkdate(twhen, &tmt); - } - - return ((tmd.tm_mday == tmt.tm_mday) && (tmd.tm_mon == tmt.tm_mon) && (tmd.tm_year == tmt.tm_year)); -} - -QString get_trip_date_string(timestamp_t when, int nr, bool getday) -{ - struct tm tm; - utc_mkdate(when, &tm); - QDateTime localTime = QDateTime::fromTime_t(when); - localTime.setTimeSpec(Qt::UTC); - QString ret ; - - QString suffix = " " + QObject::tr("(%n dive(s))", "", nr); - if (getday) { - ret = localTime.date().toString(prefs.date_format) + suffix; - } else { - ret = localTime.date().toString("MMM yy") + suffix; - } - return ret; - -} - -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; -QMutex hashOfMutex; -QHash<QByteArray, QString> localFilenameOf; -QHash <QString, QImage > thumbnailCache; - -extern "C" char * hashstring(char * filename) -{ - return hashOf[QString(filename)].toHex().data(); -} - -const QString hashfile_name() -{ - return QString(system_default_directory()).append("/hashes"); -} - -extern "C" char *hashfile_name_string() -{ - return strdup(hashfile_name().toUtf8().data()); -} - -void read_hashes() -{ - QFile hashfile(hashfile_name()); - if (hashfile.open(QIODevice::ReadOnly)) { - QDataStream stream(&hashfile); - stream >> localFilenameOf; - stream >> hashOf; - stream >> thumbnailCache; - hashfile.close(); - } -} - -void write_hashes() -{ - QSaveFile hashfile(hashfile_name()); - if (hashfile.open(QIODevice::WriteOnly)) { - QDataStream stream(&hashfile); - stream << localFilenameOf; - stream << hashOf; - stream << thumbnailCache; - hashfile.commit(); - } else { - qDebug() << "cannot open" << hashfile.fileName(); - } -} - -void add_hash(const QString filename, QByteArray hash) -{ - QMutexLocker locker(&hashOfMutex); - hashOf[filename] = hash; - localFilenameOf[hash] = filename; -} - -QByteArray hashFile(const QString filename) -{ - QCryptographicHash hash(QCryptographicHash::Sha1); - QFile imagefile(filename); - if (imagefile.exists() && imagefile.open(QIODevice::ReadOnly)) { - hash.addData(&imagefile); - add_hash(filename, hash.result()); - return hash.result(); - } else { - return QByteArray(); - } -} - -void learnHash(struct picture *picture, QByteArray hash) -{ - if (picture->hash) - free(picture->hash); - QMutexLocker locker(&hashOfMutex); - hashOf[QString(picture->filename)] = hash; - picture->hash = strdup(hash.toHex()); -} - -QString localFilePath(const QString originalFilename) -{ - if (hashOf.contains(originalFilename) && localFilenameOf.contains(hashOf[originalFilename])) - return localFilenameOf[hashOf[originalFilename]]; - else - return originalFilename; -} - -QString fileFromHash(char *hash) -{ - return localFilenameOf[QByteArray::fromHex(hash)]; -} - -// This needs to operate on a copy of picture as it frees it after finishing! -void updateHash(struct picture *picture) { - QByteArray hash = hashFile(fileFromHash(picture->hash)); - learnHash(picture, hash); - picture_free(picture); -} - -// This needs to operate on a copy of picture as it frees it after finishing! -void hashPicture(struct picture *picture) -{ - char *oldHash = copy_string(picture->hash); - learnHash(picture, hashFile(QString(picture->filename))); - if (!same_string(picture->hash, "") && !same_string(picture->hash, oldHash)) - mark_divelist_changed((true)); - free(oldHash); - picture_free(picture); -} - -extern "C" void cache_picture(struct picture *picture) -{ - QString filename = picture->filename; - if (!hashOf.contains(filename)) - QtConcurrent::run(hashPicture, clone_picture(picture)); -} - -void learnImages(const QDir dir, int max_recursions) -{ - QStringList filters, files; - - if (max_recursions) { - foreach (QString dirname, dir.entryList(QStringList(), QDir::NoDotAndDotDot | QDir::Dirs)) { - learnImages(QDir(dir.filePath(dirname)), max_recursions - 1); - } - } - - 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); -} - -extern "C" const char *local_file_path(struct picture *picture) -{ - QString hashString = picture->hash; - if (hashString.isEmpty()) { - QByteArray hash = hashFile(picture->filename); - free(picture->hash); - picture->hash = strdup(hash.toHex().data()); - } - QString localFileName = fileFromHash(picture->hash); - if (localFileName.isEmpty()) - localFileName = picture->filename; - return strdup(qPrintable(localFileName)); -} - -extern "C" bool picture_exists(struct picture *picture) -{ - QString localFilename = fileFromHash(picture->hash); - QByteArray hash = hashFile(localFilename); - return same_string(hash.toHex().data(), picture->hash); -} - -const QString picturedir() -{ - return QString(system_default_directory()).append("/picturedata/"); -} - -extern "C" char *picturedir_string() -{ - return strdup(picturedir().toUtf8().data()); -} - -/* when we get a picture from git storage (local or remote) and can't find the picture - * based on its hash, we create a local copy with the hash as filename and the appropriate - * suffix */ -extern "C" void savePictureLocal(struct picture *picture, const char *data, int len) -{ - QString dirname = picturedir(); - QDir localPictureDir(dirname); - localPictureDir.mkpath(dirname); - QString suffix(picture->filename); - suffix.replace(QRegularExpression(".*\\."), ""); - QString filename(dirname + picture->hash + "." + suffix); - QSaveFile out(filename); - if (out.open(QIODevice::WriteOnly)) { - out.write(data, len); - out.commit(); - add_hash(filename, QByteArray::fromHex(picture->hash)); - } -} - -extern "C" void picture_load_exif_data(struct picture *p) -{ - EXIFInfo exif; - memblock mem; - - if (readfile(localFilePath(QString(p->filename)).toUtf8().data(), &mem) <= 0) - goto picture_load_exit; - if (exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size) != PARSE_EXIF_SUCCESS) - goto picture_load_exit; - 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; -} - -QString get_gas_string(struct gasmix gas) -{ - uint o2 = (get_o2(&gas) + 5) / 10, he = (get_he(&gas) + 5) / 10; - QString result = gasmix_is_air(&gas) ? QObject::tr("AIR") : he == 0 ? (o2 == 100 ? QObject::tr("OXYGEN") : QString("EAN%1").arg(o2, 2, 10, QChar('0'))) : QString("%1/%2").arg(o2).arg(he); - return result; -} - -QString get_divepoint_gas_string(const divedatapoint &p) -{ - return get_gas_string(p.gasmix); -} - -weight_t string_to_weight(const char *str) -{ - const char *end; - double value = strtod_flags(str, &end, 0); - QString rest = QString(end).trimmed(); - QString local_kg = QObject::tr("kg"); - QString local_lbs = QObject::tr("lbs"); - weight_t weight; - - if (rest.startsWith("kg") || rest.startsWith(local_kg)) - goto kg; - // using just "lb" instead of "lbs" is intentional - some people might enter the singular - if (rest.startsWith("lb") || rest.startsWith(local_lbs)) - goto lbs; - if (prefs.units.weight == prefs.units.LBS) - goto lbs; -kg: - weight.grams = rint(value * 1000); - return weight; -lbs: - weight.grams = lbs_to_grams(value); - return weight; -} - -depth_t string_to_depth(const char *str) -{ - const char *end; - double value = strtod_flags(str, &end, 0); - QString rest = QString(end).trimmed(); - QString local_ft = QObject::tr("ft"); - QString local_m = QObject::tr("m"); - depth_t depth; - - if (rest.startsWith("m") || rest.startsWith(local_m)) - goto m; - if (rest.startsWith("ft") || rest.startsWith(local_ft)) - goto ft; - if (prefs.units.length == prefs.units.FEET) - goto ft; -m: - depth.mm = rint(value * 1000); - return depth; -ft: - depth.mm = feet_to_mm(value); - return depth; -} - -pressure_t string_to_pressure(const char *str) -{ - const char *end; - double value = strtod_flags(str, &end, 0); - QString rest = QString(end).trimmed(); - QString local_psi = QObject::tr("psi"); - QString local_bar = QObject::tr("bar"); - pressure_t pressure; - - if (rest.startsWith("bar") || rest.startsWith(local_bar)) - goto bar; - if (rest.startsWith("psi") || rest.startsWith(local_psi)) - goto psi; - if (prefs.units.pressure == prefs.units.PSI) - goto psi; -bar: - pressure.mbar = rint(value * 1000); - return pressure; -psi: - pressure.mbar = psi_to_mbar(value); - return pressure; -} - -volume_t string_to_volume(const char *str, pressure_t workp) -{ - const char *end; - double value = strtod_flags(str, &end, 0); - QString rest = QString(end).trimmed(); - QString local_l = QObject::tr("l"); - QString local_cuft = QObject::tr("cuft"); - volume_t volume; - - if (rest.startsWith("l") || rest.startsWith("â„“") || rest.startsWith(local_l)) - goto l; - if (rest.startsWith("cuft") || rest.startsWith(local_cuft)) - goto cuft; - /* - * If we don't have explicit units, and there is no working - * pressure, we're going to assume "liter" even in imperial - * measurements. - */ - if (!workp.mbar) - goto l; - if (prefs.units.volume == prefs.units.LITER) - goto l; -cuft: - if (workp.mbar) - value /= bar_to_atm(workp.mbar / 1000.0); - value = cuft_to_l(value); -l: - volume.mliter = rint(value * 1000); - return volume; -} - -fraction_t string_to_fraction(const char *str) -{ - const char *end; - double value = strtod_flags(str, &end, 0); - fraction_t fraction; - - fraction.permille = rint(value * 10); - return fraction; -} - -int getCloudURL(QString &filename) -{ - QString email = QString(prefs.cloud_storage_email); - email.replace(QRegularExpression("[^a-zA-Z0-9@._+-]"), ""); - if (email.isEmpty() || same_string(prefs.cloud_storage_password, "")) - return report_error("Please configure Cloud storage email and password in the preferences"); - if (email != prefs.cloud_storage_email_encoded) { - free(prefs.cloud_storage_email_encoded); - prefs.cloud_storage_email_encoded = strdup(qPrintable(email)); - } - filename = QString(QString(prefs.cloud_git_url) + "/%1[%1]").arg(email); - if (verbose) - qDebug() << "cloud URL set as" << filename; - return 0; -} - -extern "C" char *cloud_url() -{ - QString filename; - getCloudURL(filename); - return strdup(filename.toUtf8().data()); -} - -void loadPreferences() -{ - QSettings s; - QVariant v; - - uiLanguage(NULL); - s.beginGroup("Units"); - if (s.value("unit_system").toString() == "metric") { - prefs.unit_system = METRIC; - prefs.units = SI_units; - } else if (s.value("unit_system").toString() == "imperial") { - prefs.unit_system = IMPERIAL; - prefs.units = IMPERIAL_units; - } else { - prefs.unit_system = PERSONALIZE; - GET_UNIT("length", length, units::FEET, units::METERS); - GET_UNIT("pressure", pressure, units::PSI, units::BAR); - GET_UNIT("volume", volume, units::CUFT, units::LITER); - GET_UNIT("temperature", temperature, units::FAHRENHEIT, units::CELSIUS); - GET_UNIT("weight", weight, units::LBS, units::KG); - } - GET_UNIT("vertical_speed_time", vertical_speed_time, units::MINUTES, units::SECONDS); - GET_BOOL("coordinates", coordinates_traditional); - s.endGroup(); - s.beginGroup("TecDetails"); - GET_BOOL("po2graph", pp_graphs.po2); - GET_BOOL("pn2graph", pp_graphs.pn2); - GET_BOOL("phegraph", pp_graphs.phe); - GET_DOUBLE("po2threshold", pp_graphs.po2_threshold); - GET_DOUBLE("pn2threshold", pp_graphs.pn2_threshold); - GET_DOUBLE("phethreshold", pp_graphs.phe_threshold); - GET_BOOL("mod", mod); - GET_DOUBLE("modpO2", modpO2); - GET_BOOL("ead", ead); - GET_BOOL("redceiling", redceiling); - GET_BOOL("dcceiling", dcceiling); - GET_BOOL("calcceiling", calcceiling); - GET_BOOL("calcceiling3m", calcceiling3m); - GET_BOOL("calcndltts", calcndltts); - GET_BOOL("calcalltissues", calcalltissues); - GET_BOOL("hrgraph", hrgraph); - GET_BOOL("tankbar", tankbar); - GET_BOOL("RulerBar", rulergraph); - GET_BOOL("percentagegraph", percentagegraph); - GET_INT("gflow", gflow); - GET_INT("gfhigh", gfhigh); - GET_BOOL("gf_low_at_maxdepth", gf_low_at_maxdepth); - GET_BOOL("show_ccr_setpoint",show_ccr_setpoint); - GET_BOOL("show_ccr_sensors",show_ccr_sensors); - GET_BOOL("zoomed_plot", zoomed_plot); - set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); - GET_BOOL("show_sac", show_sac); - GET_BOOL("display_unused_tanks", display_unused_tanks); - GET_BOOL("show_average_depth", show_average_depth); - s.endGroup(); - - s.beginGroup("GeneralSettings"); - GET_TXT("default_filename", default_filename); - GET_INT("default_file_behavior", default_file_behavior); - if (prefs.default_file_behavior == UNDEFINED_DEFAULT_FILE) { - // undefined, so check if there's a filename set and - // use that, otherwise go with no default file - if (QString(prefs.default_filename).isEmpty()) - prefs.default_file_behavior = NO_DEFAULT_FILE; - else - prefs.default_file_behavior = LOCAL_DEFAULT_FILE; - } - GET_TXT("default_cylinder", default_cylinder); - GET_BOOL("use_default_file", use_default_file); - GET_INT("defaultsetpoint", defaultsetpoint); - GET_INT("o2consumption", o2consumption); - GET_INT("pscr_ratio", pscr_ratio); - s.endGroup(); - - s.beginGroup("Display"); - // get the font from the settings or our defaults - // respect the system default font size if none is explicitly set - QFont defaultFont = s.value("divelist_font", prefs.divelist_font).value<QFont>(); - if (IS_FP_SAME(system_divelist_default_font_size, -1.0)) { - prefs.font_size = qApp->font().pointSizeF(); - system_divelist_default_font_size = prefs.font_size; // this way we don't save it on exit - } - prefs.font_size = s.value("font_size", prefs.font_size).toFloat(); - // painful effort to ignore previous default fonts on Windows - ridiculous - QString fontName = defaultFont.toString(); - if (fontName.contains(",")) - fontName = fontName.left(fontName.indexOf(",")); - if (subsurface_ignore_font(fontName.toUtf8().constData())) { - defaultFont = QFont(prefs.divelist_font); - } else { - free((void *)prefs.divelist_font); - prefs.divelist_font = strdup(fontName.toUtf8().constData()); - } - defaultFont.setPointSizeF(prefs.font_size); - qApp->setFont(defaultFont); - GET_INT("displayinvalid", display_invalid_dives); - s.endGroup(); - - s.beginGroup("Animations"); - GET_INT("animation_speed", animation_speed); - s.endGroup(); - - s.beginGroup("Network"); - GET_INT_DEF("proxy_type", proxy_type, QNetworkProxy::DefaultProxy); - GET_TXT("proxy_host", proxy_host); - GET_INT("proxy_port", proxy_port); - GET_BOOL("proxy_auth", proxy_auth); - GET_TXT("proxy_user", proxy_user); - GET_TXT("proxy_pass", proxy_pass); - s.endGroup(); - - s.beginGroup("CloudStorage"); - GET_TXT("email", cloud_storage_email); -#ifndef SUBSURFACE_MOBILE - GET_BOOL("save_password_local", save_password_local); -#else - // always save the password in Subsurface-mobile - prefs.save_password_local = true; -#endif - if (prefs.save_password_local) { // GET_TEXT macro is not a single statement - GET_TXT("password", cloud_storage_password); - } - GET_INT("cloud_verification_status", cloud_verification_status); - GET_BOOL("cloud_background_sync", cloud_background_sync); - GET_BOOL("git_local_only", git_local_only); - - // creating the git url here is simply a convenience when C code wants - // to compare against that git URL - it's always derived from the base URL - GET_TXT("cloud_base_url", cloud_base_url); - prefs.cloud_git_url = strdup(qPrintable(QString(prefs.cloud_base_url) + "/git")); - s.endGroup(); - - // Subsurface webservice id is stored outside of the groups - GET_TXT("subsurface_webservice_uid", userid); - - // but the related time / distance threshold (only used in the mobile app) - // are in their own group - s.beginGroup("locationService"); - GET_INT("distance_threshold", distance_threshold); - GET_INT("time_threshold", time_threshold); - s.endGroup(); - - // GeoManagement - s.beginGroup("geocoding"); -#ifdef DISABLED - GET_BOOL("enable_geocoding", geocoding.enable_geocoding); - GET_BOOL("parse_dive_without_gps", geocoding.parse_dive_without_gps); - GET_BOOL("tag_existing_dives", geocoding.tag_existing_dives); -#else - prefs.geocoding.enable_geocoding = true; -#endif - GET_ENUM("cat0", taxonomy_category, geocoding.category[0]); - GET_ENUM("cat1", taxonomy_category, geocoding.category[1]); - GET_ENUM("cat2", taxonomy_category, geocoding.category[2]); - s.endGroup(); - - // GPS service time and distance thresholds - s.beginGroup("LocationService"); - GET_INT("time_threshold", time_threshold); - GET_INT("distance_threshold", distance_threshold); - s.endGroup(); -} - -extern "C" bool isCloudUrl(const char *filename) -{ - QString email = QString(prefs.cloud_storage_email); - email.replace(QRegularExpression("[^a-zA-Z0-9@._+-]"), ""); - if (!email.isEmpty() && - QString(QString(prefs.cloud_git_url) + "/%1[%1]").arg(email) == filename) - return true; - return false; -} - -extern "C" bool getProxyString(char **buffer) -{ - if (prefs.proxy_type == QNetworkProxy::HttpProxy) { - QString proxy; - if (prefs.proxy_auth) - proxy = QString("http://%1:%2@%3:%4").arg(prefs.proxy_user).arg(prefs.proxy_pass) - .arg(prefs.proxy_host).arg(prefs.proxy_port); - else - proxy = QString("http://%1:%2").arg(prefs.proxy_host).arg(prefs.proxy_port); - if (buffer) - *buffer = strdup(qPrintable(proxy)); - return true; - } - return false; -} - -extern "C" void subsurface_mkdir(const char *dir) -{ - QDir directory; - if (!directory.mkpath(QString(dir))) - qDebug() << "failed to create path" << dir; -} - -extern "C" void parse_display_units(char *line) -{ - qDebug() << line; -} - -static QByteArray currentApplicationState; - -QByteArray getCurrentAppState() -{ - return currentApplicationState; -} - -void setCurrentAppState(QByteArray state) -{ - currentApplicationState = state; -} - -extern "C" bool in_planner() -{ - return (currentApplicationState == "PlanDive" || currentApplicationState == "EditPlannedDive"); -} - -void init_proxy() -{ - QNetworkProxy proxy; - proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type)); - proxy.setHostName(prefs.proxy_host); - proxy.setPort(prefs.proxy_port); - if (prefs.proxy_auth) { - proxy.setUser(prefs.proxy_user); - proxy.setPassword(prefs.proxy_pass); - } - QNetworkProxy::setApplicationProxy(proxy); -} - -QString getUUID() -{ - QString uuidString; - QSettings settings; - settings.beginGroup("UpdateManager"); - if (settings.contains("UUID")) { - uuidString = settings.value("UUID").toString(); - } else { - QUuid uuid = QUuid::createUuid(); - uuidString = uuid.toString(); - settings.setValue("UUID", uuidString); - } - uuidString.replace("{", "").replace("}", ""); - return uuidString; -} diff --git a/subsurface-core/qthelper.h b/subsurface-core/qthelper.h deleted file mode 100644 index 3a5ef60e4..000000000 --- a/subsurface-core/qthelper.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef QTHELPER_H -#define QTHELPER_H - -#include <QMultiMap> -#include <QString> -#include <stdint.h> -#include "dive.h" -#include "divelist.h" -#include <QTranslator> -#include <QDir> - -// global pointers for our translation -extern QTranslator *qtTranslator, *ssrfTranslator; - -QString weight_string(int weight_in_grams); -QString distance_string(int distanceInMeters); -bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out = 0); -extern "C" const char *printGPSCoords(int lat, int lon); -QList<int> getDivesInTrip(dive_trip_t *trip); -QString get_gas_string(struct gasmix gas); -QString get_divepoint_gas_string(const divedatapoint& dp); -void read_hashes(); -void write_hashes(); -void updateHash(struct picture *picture); -QByteArray hashFile(const QString filename); -void learnImages(const QDir dir, int max_recursions); -void add_hash(const QString filename, QByteArray hash); -void hashPicture(struct picture *picture); -QString localFilePath(const QString originalFilename); -QString fileFromHash(char *hash); -void learnHash(struct picture *picture, QByteArray hash); -extern "C" void cache_picture(struct picture *picture); -weight_t string_to_weight(const char *str); -depth_t string_to_depth(const char *str); -pressure_t string_to_pressure(const char *str); -volume_t string_to_volume(const char *str, pressure_t workp); -fraction_t string_to_fraction(const char *str); -int getCloudURL(QString &filename); -void loadPreferences(); -bool parseGpsText(const QString &gps_text, double *latitude, double *longitude); -QByteArray getCurrentAppState(); -void setCurrentAppState(QByteArray state); -extern "C" bool in_planner(); -extern "C" void subsurface_mkdir(const char *dir); -void init_proxy(); -QString getUUID(); - -#endif // QTHELPER_H diff --git a/subsurface-core/qthelperfromc.h b/subsurface-core/qthelperfromc.h deleted file mode 100644 index 32aed8949..000000000 --- a/subsurface-core/qthelperfromc.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef QTHELPERFROMC_H -#define QTHELPERFROMC_H - -bool getProxyString(char **buffer); -bool canReachCloudServer(); -void updateWindowTitle(); -bool isCloudUrl(const char *filename); -void subsurface_mkdir(const char *dir); -char *get_file_name(const char *fileName); -void copy_image_and_overwrite(const char *cfileName, const char *path, const char *cnewName); -char *hashstring(char *filename); -bool picture_exists(struct picture *picture); -char *move_away(const char *path); -const char *local_file_path(struct picture *picture); -void savePictureLocal(struct picture *picture, const char *data, int len); -void cache_picture(struct picture *picture); -char *cloud_url(); -char *hashfile_name_string(); -char *picturedir_string(); -const char *subsurface_user_agent(); - -#endif // QTHELPERFROMC_H diff --git a/subsurface-core/qtserialbluetooth.cpp b/subsurface-core/qtserialbluetooth.cpp deleted file mode 100644 index 6b104157a..000000000 --- a/subsurface-core/qtserialbluetooth.cpp +++ /dev/null @@ -1,416 +0,0 @@ -#include <errno.h> - -#include <QtBluetooth/QBluetoothAddress> -#include <QtBluetooth/QBluetoothSocket> -#include <QEventLoop> -#include <QTimer> -#include <QDebug> - -#include <libdivecomputer/version.h> - -#if defined(SSRF_CUSTOM_SERIAL) - -#if defined(Q_OS_WIN) - #include <winsock2.h> - #include <windows.h> - #include <ws2bth.h> -#endif - -#include <libdivecomputer/custom_serial.h> - -extern "C" { -typedef struct serial_t { - /* Library context. */ - dc_context_t *context; - /* - * RFCOMM socket used for Bluetooth Serial communication. - */ -#if defined(Q_OS_WIN) - SOCKET socket; -#else - QBluetoothSocket *socket; -#endif - long timeout; -} serial_t; - -static int qt_serial_open(serial_t **out, dc_context_t *context, const char* devaddr) -{ - if (out == NULL) - return DC_STATUS_INVALIDARGS; - - // Allocate memory. - serial_t *serial_port = (serial_t *) malloc (sizeof (serial_t)); - if (serial_port == NULL) { - return DC_STATUS_NOMEMORY; - } - - // Library context. - serial_port->context = context; - - // Default to blocking reads. - serial_port->timeout = -1; - -#if defined(Q_OS_WIN) - // Create a RFCOMM socket - serial_port->socket = ::socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); - - if (serial_port->socket == INVALID_SOCKET) { - free(serial_port); - return DC_STATUS_IO; - } - - SOCKADDR_BTH socketBthAddress; - int socketBthAddressBth = sizeof (socketBthAddress); - char *address = strdup(devaddr); - - ZeroMemory(&socketBthAddress, socketBthAddressBth); - qDebug() << "Trying to connect to address " << devaddr; - - if (WSAStringToAddressA(address, - AF_BTH, - NULL, - (LPSOCKADDR) &socketBthAddress, - &socketBthAddressBth - ) != 0) { - qDebug() << "FAiled to convert the address " << address; - free(address); - - return DC_STATUS_IO; - } - - free(address); - - socketBthAddress.addressFamily = AF_BTH; - socketBthAddress.port = BT_PORT_ANY; - memset(&socketBthAddress.serviceClassId, 0, sizeof(socketBthAddress.serviceClassId)); - socketBthAddress.serviceClassId = SerialPortServiceClass_UUID; - - // Try to connect to the device - if (::connect(serial_port->socket, - (struct sockaddr *) &socketBthAddress, - socketBthAddressBth - ) != 0) { - qDebug() << "Failed to connect to device"; - - return DC_STATUS_NODEVICE; - } - - qDebug() << "Succesfully connected to device"; -#else - // Create a RFCOMM socket - serial_port->socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol); - - // Wait until the connection succeeds or until an error occurs - QEventLoop loop; - loop.connect(serial_port->socket, SIGNAL(connected()), SLOT(quit())); - loop.connect(serial_port->socket, SIGNAL(error(QBluetoothSocket::SocketError)), SLOT(quit())); - - // Create a timer. If the connection doesn't succeed after five seconds or no error occurs then stop the opening step - QTimer timer; - int msec = 5000; - timer.setSingleShot(true); - loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); - -#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) - // First try to connect on RFCOMM channel 1. This is the default channel for most devices - QBluetoothAddress remoteDeviceAddress(devaddr); - serial_port->socket->connectToService(remoteDeviceAddress, 1, QIODevice::ReadWrite | QIODevice::Unbuffered); - timer.start(msec); - loop.exec(); - - if (serial_port->socket->state() == QBluetoothSocket::ConnectingState) { - // It seems that the connection on channel 1 took more than expected. Wait another 15 seconds - qDebug() << "The connection on RFCOMM channel number 1 took more than expected. Wait another 15 seconds."; - timer.start(3 * msec); - loop.exec(); - } else if (serial_port->socket->state() == QBluetoothSocket::UnconnectedState) { - // Try to connect on channel number 5. Maybe this is a Shearwater Petrel2 device. - qDebug() << "Connection on channel 1 failed. Trying on channel number 5."; - serial_port->socket->connectToService(remoteDeviceAddress, 5, QIODevice::ReadWrite | QIODevice::Unbuffered); - timer.start(msec); - loop.exec(); - - if (serial_port->socket->state() == QBluetoothSocket::ConnectingState) { - // It seems that the connection on channel 5 took more than expected. Wait another 15 seconds - qDebug() << "The connection on RFCOMM channel number 5 took more than expected. Wait another 15 seconds."; - timer.start(3 * msec); - loop.exec(); - } - } -#elif defined(Q_OS_ANDROID) || (QT_VERSION >= 0x050500 && defined(Q_OS_MAC)) - // Try to connect to the device using the uuid of the Serial Port Profile service - QBluetoothAddress remoteDeviceAddress(devaddr); - serial_port->socket->connectToService(remoteDeviceAddress, QBluetoothUuid(QBluetoothUuid::SerialPort)); - timer.start(msec); - loop.exec(); - - if (serial_port->socket->state() == QBluetoothSocket::ConnectingState || - serial_port->socket->state() == QBluetoothSocket::ServiceLookupState) { - // It seems that the connection step took more than expected. Wait another 20 seconds. - qDebug() << "The connection step took more than expected. Wait another 20 seconds"; - timer.start(4 * msec); - loop.exec(); - } -#endif - if (serial_port->socket->state() != QBluetoothSocket::ConnectedState) { - - // Get the latest error and try to match it with one from libdivecomputer - QBluetoothSocket::SocketError err = serial_port->socket->error(); - qDebug() << "Failed to connect to device " << devaddr << ". Device state " << serial_port->socket->state() << ". Error: " << err; - - free (serial_port); - switch(err) { - case QBluetoothSocket::HostNotFoundError: - case QBluetoothSocket::ServiceNotFoundError: - return DC_STATUS_NODEVICE; - case QBluetoothSocket::UnsupportedProtocolError: - return DC_STATUS_PROTOCOL; -#if QT_VERSION >= 0x050400 - case QBluetoothSocket::OperationError: - return DC_STATUS_UNSUPPORTED; -#endif - case QBluetoothSocket::NetworkError: - return DC_STATUS_IO; - default: - return QBluetoothSocket::UnknownSocketError; - } - } -#endif - *out = serial_port; - - return DC_STATUS_SUCCESS; -} - -static int qt_serial_close(serial_t *device) -{ - if (device == NULL) - return DC_STATUS_SUCCESS; - -#if defined(Q_OS_WIN) - // Cleanup - closesocket(device->socket); - free(device); -#else - if (device->socket == NULL) { - free(device); - return DC_STATUS_SUCCESS; - } - - device->socket->close(); - - delete device->socket; - free(device); -#endif - - return DC_STATUS_SUCCESS; -} - -static int qt_serial_read(serial_t *device, void* data, unsigned int size) -{ -#if defined(Q_OS_WIN) - if (device == NULL) - return DC_STATUS_INVALIDARGS; - - unsigned int nbytes = 0; - int rc; - - while (nbytes < size) { - rc = recv (device->socket, (char *) data + nbytes, size - nbytes, 0); - - if (rc < 0) { - return -1; // Error during recv call. - } else if (rc == 0) { - break; // EOF reached. - } - - nbytes += rc; - } - - return nbytes; -#else - if (device == NULL || device->socket == NULL) - return DC_STATUS_INVALIDARGS; - - unsigned int nbytes = 0; - int rc; - - while(nbytes < size && device->socket->state() == QBluetoothSocket::ConnectedState) - { - rc = device->socket->read((char *) data + nbytes, size - nbytes); - - if (rc < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; // Retry. - - return -1; // Something really bad happened :-( - } else if (rc == 0) { - // Wait until the device is available for read operations - QEventLoop loop; - QTimer timer; - timer.setSingleShot(true); - loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); - loop.connect(device->socket, SIGNAL(readyRead()), SLOT(quit())); - timer.start(device->timeout); - loop.exec(); - - if (!timer.isActive()) - return nbytes; - } - - nbytes += rc; - } - - return nbytes; -#endif -} - -static int qt_serial_write(serial_t *device, const void* data, unsigned int size) -{ -#if defined(Q_OS_WIN) - if (device == NULL) - return DC_STATUS_INVALIDARGS; - - unsigned int nbytes = 0; - int rc; - - while (nbytes < size) { - rc = send(device->socket, (char *) data + nbytes, size - nbytes, 0); - - if (rc < 0) { - return -1; // Error during send call. - } - - nbytes += rc; - } - - return nbytes; -#else - if (device == NULL || device->socket == NULL) - return DC_STATUS_INVALIDARGS; - - unsigned int nbytes = 0; - int rc; - - while(nbytes < size && device->socket->state() == QBluetoothSocket::ConnectedState) - { - rc = device->socket->write((char *) data + nbytes, size - nbytes); - - if (rc < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; // Retry. - - return -1; // Something really bad happened :-( - } else if (rc == 0) { - break; - } - - nbytes += rc; - } - - return nbytes; -#endif -} - -static int qt_serial_flush(serial_t *device, int queue) -{ - (void)queue; - if (device == NULL) - return DC_STATUS_INVALIDARGS; -#if !defined(Q_OS_WIN) - if (device->socket == NULL) - return DC_STATUS_INVALIDARGS; -#endif - // TODO: add implementation - - return DC_STATUS_SUCCESS; -} - -static int qt_serial_get_received(serial_t *device) -{ -#if defined(Q_OS_WIN) - if (device == NULL) - return DC_STATUS_INVALIDARGS; - - // TODO use WSAIoctl to get the information - - return 0; -#else - if (device == NULL || device->socket == NULL) - return DC_STATUS_INVALIDARGS; - - return device->socket->bytesAvailable(); -#endif -} - -static int qt_serial_get_transmitted(serial_t *device) -{ -#if defined(Q_OS_WIN) - if (device == NULL) - return DC_STATUS_INVALIDARGS; - - // TODO add implementation - - return 0; -#else - if (device == NULL || device->socket == NULL) - return DC_STATUS_INVALIDARGS; - - return device->socket->bytesToWrite(); -#endif -} - -static int qt_serial_set_timeout(serial_t *device, long timeout) -{ - if (device == NULL) - return DC_STATUS_INVALIDARGS; - - device->timeout = timeout; - - return DC_STATUS_SUCCESS; -} - - -const dc_serial_operations_t qt_serial_ops = { - .open = qt_serial_open, - .close = qt_serial_close, - .read = qt_serial_read, - .write = qt_serial_write, - .flush = qt_serial_flush, - .get_received = qt_serial_get_received, - .get_transmitted = qt_serial_get_transmitted, - .set_timeout = qt_serial_set_timeout -}; - -extern void dc_serial_init (dc_serial_t *serial, void *data, const dc_serial_operations_t *ops); - -dc_status_t dc_serial_qt_open(dc_serial_t **out, dc_context_t *context, const char *devaddr) -{ - if (out == NULL) - return DC_STATUS_INVALIDARGS; - - // Allocate memory. - dc_serial_t *serial_device = (dc_serial_t *) malloc (sizeof (dc_serial_t)); - - if (serial_device == NULL) { - return DC_STATUS_NOMEMORY; - } - - // Initialize data and function pointers - dc_serial_init(serial_device, NULL, &qt_serial_ops); - - // Open the serial device. - dc_status_t rc = (dc_status_t)qt_serial_open (&serial_device->port, context, devaddr); - if (rc != DC_STATUS_SUCCESS) { - free (serial_device); - return rc; - } - - // Set the type of the device - serial_device->type = DC_TRANSPORT_BLUETOOTH; - - *out = serial_device; - - return DC_STATUS_SUCCESS; -} -} -#endif diff --git a/subsurface-core/save-git.c b/subsurface-core/save-git.c deleted file mode 100644 index e22019ab0..000000000 --- a/subsurface-core/save-git.c +++ /dev/null @@ -1,1249 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include <stdio.h> -#include <ctype.h> -#include <string.h> -#include <stdlib.h> -#include <errno.h> -#include <time.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <fcntl.h> -#include <git2.h> - -#include "dive.h" -#include "divelist.h" -#include "device.h" -#include "membuffer.h" -#include "git-access.h" -#include "version.h" -#include "qthelperfromc.h" - -#define VA_BUF(b, fmt) do { va_list args; va_start(args, fmt); put_vformat(b, fmt, args); va_end(args); } while (0) - -static void cond_put_format(int cond, struct membuffer *b, const char *fmt, ...) -{ - if (cond) { - VA_BUF(b, fmt); - } -} - -#define SAVE(str, x) cond_put_format(dive->x, b, str " %d\n", dive->x) - -static void show_gps(struct membuffer *b, degrees_t latitude, degrees_t longitude) -{ - if (latitude.udeg || longitude.udeg) { - put_degrees(b, latitude, "gps ", " "); - put_degrees(b, longitude, "", "\n"); - } -} - -static void quote(struct membuffer *b, const char *text) -{ - const char *p = text; - - for (;;) { - const char *escape; - - switch (*p++) { - default: - continue; - case 0: - escape = NULL; - break; - case 1 ... 8: - case 11: - case 12: - case 14 ... 31: - escape = "?"; - break; - case '\\': - escape = "\\\\"; - break; - case '"': - escape = "\\\""; - break; - case '\n': - escape = "\n\t"; - if (*p == '\n') - escape = "\n"; - break; - } - put_bytes(b, text, (p - text - 1)); - if (!escape) - break; - put_string(b, escape); - text = p; - } -} - -static void show_utf8(struct membuffer *b, const char *prefix, const char *value, const char *postfix) -{ - if (value) { - put_format(b, "%s\"", prefix); - quote(b, value); - put_format(b, "\"%s", postfix); - } -} - -static void save_overview(struct membuffer *b, struct dive *dive) -{ - show_utf8(b, "divemaster ", dive->divemaster, "\n"); - show_utf8(b, "buddy ", dive->buddy, "\n"); - show_utf8(b, "suit ", dive->suit, "\n"); - show_utf8(b, "notes ", dive->notes, "\n"); -} - -static void save_tags(struct membuffer *b, struct tag_entry *tags) -{ - const char *sep = " "; - - if (!tags) - return; - put_string(b, "tags"); - while (tags) { - show_utf8(b, sep, tags->tag->source ? : tags->tag->name, ""); - sep = ", "; - tags = tags->next; - } - put_string(b, "\n"); -} - -static void save_extra_data(struct membuffer *b, struct extra_data *ed) -{ - while (ed) { - if (ed->key && ed->value) - put_format(b, "keyvalue \"%s\" \"%s\"\n", ed->key ? : "", ed->value ? : ""); - ed = ed->next; - } -} - -static void put_gasmix(struct membuffer *b, struct gasmix *mix) -{ - int o2 = mix->o2.permille; - int he = mix->he.permille; - - if (o2) { - put_format(b, " o2=%u.%u%%", FRACTION(o2, 10)); - if (he) - put_format(b, " he=%u.%u%%", FRACTION(he, 10)); - } -} - -static void save_cylinder_info(struct membuffer *b, struct dive *dive) -{ - int i, nr; - - nr = nr_cylinders(dive); - for (i = 0; i < nr; i++) { - cylinder_t *cylinder = dive->cylinder + i; - int volume = cylinder->type.size.mliter; - const char *description = cylinder->type.description; - - put_string(b, "cylinder"); - if (volume) - put_milli(b, " vol=", volume, "l"); - put_pressure(b, cylinder->type.workingpressure, " workpressure=", "bar"); - show_utf8(b, " description=", description, ""); - strip_mb(b); - put_gasmix(b, &cylinder->gasmix); - put_pressure(b, cylinder->start, " start=", "bar"); - put_pressure(b, cylinder->end, " end=", "bar"); - if (cylinder->cylinder_use != OC_GAS) - put_format(b, " use=%s", cylinderuse_text[cylinder->cylinder_use]); - - put_string(b, "\n"); - } -} - -static void save_weightsystem_info(struct membuffer *b, struct dive *dive) -{ - int i, nr; - - nr = nr_weightsystems(dive); - for (i = 0; i < nr; i++) { - weightsystem_t *ws = dive->weightsystem + i; - int grams = ws->weight.grams; - const char *description = ws->description; - - put_string(b, "weightsystem"); - put_milli(b, " weight=", grams, "kg"); - show_utf8(b, " description=", description, ""); - put_string(b, "\n"); - } -} - -static void save_dive_temperature(struct membuffer *b, struct dive *dive) -{ - if (dive->airtemp.mkelvin != dc_airtemp(&dive->dc)) - put_temperature(b, dive->airtemp, "airtemp ", "°C\n"); - if (dive->watertemp.mkelvin != dc_watertemp(&dive->dc)) - put_temperature(b, dive->watertemp, "watertemp ", "°C\n"); -} - -static void save_depths(struct membuffer *b, struct divecomputer *dc) -{ - put_depth(b, dc->maxdepth, "maxdepth ", "m\n"); - put_depth(b, dc->meandepth, "meandepth ", "m\n"); -} - -static void save_temperatures(struct membuffer *b, struct divecomputer *dc) -{ - put_temperature(b, dc->airtemp, "airtemp ", "°C\n"); - put_temperature(b, dc->watertemp, "watertemp ", "°C\n"); -} - -static void save_airpressure(struct membuffer *b, struct divecomputer *dc) -{ - put_pressure(b, dc->surface_pressure, "surfacepressure ", "bar\n"); -} - -static void save_salinity(struct membuffer *b, struct divecomputer *dc) -{ - /* only save if we have a value that isn't the default of sea water */ - if (!dc->salinity || dc->salinity == SEAWATER_SALINITY) - return; - put_salinity(b, dc->salinity, "salinity ", "g/l\n"); -} - -static void show_date(struct membuffer *b, timestamp_t when) -{ - struct tm tm; - - utc_mkdate(when, &tm); - - put_format(b, "date %04u-%02u-%02u\n", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); - put_format(b, "time %02u:%02u:%02u\n", - tm.tm_hour, tm.tm_min, tm.tm_sec); -} - -static void show_index(struct membuffer *b, int value, const char *pre, const char *post) -{ - if (value) - put_format(b, " %s%d%s", pre, value, post); -} - -/* - * Samples are saved as densely as possible while still being readable, - * since they are the bulk of the data. - * - * For parsing, look at the units to figure out what the numbers are. - */ -static void save_sample(struct membuffer *b, struct sample *sample, struct sample *old) -{ - put_format(b, "%3u:%02u", FRACTION(sample->time.seconds, 60)); - put_milli(b, " ", sample->depth.mm, "m"); - put_temperature(b, sample->temperature, " ", "°C"); - put_pressure(b, sample->cylinderpressure, " ", "bar"); - put_pressure(b, sample->o2cylinderpressure," o2pressure=","bar"); - - /* - * We only show sensor information for samples with pressure, and only if it - * changed from the previous sensor we showed. - */ - if (sample->cylinderpressure.mbar && sample->sensor != old->sensor) { - put_format(b, " sensor=%d", sample->sensor); - old->sensor = sample->sensor; - } - - /* the deco/ndl values are stored whenever they change */ - if (sample->ndl.seconds != old->ndl.seconds) { - put_format(b, " ndl=%u:%02u", FRACTION(sample->ndl.seconds, 60)); - old->ndl = sample->ndl; - } - if (sample->tts.seconds != old->tts.seconds) { - put_format(b, " tts=%u:%02u", FRACTION(sample->tts.seconds, 60)); - old->tts = sample->tts; - } - if (sample->in_deco != old->in_deco) { - put_format(b, " in_deco=%d", sample->in_deco ? 1 : 0); - old->in_deco = sample->in_deco; - } - if (sample->stoptime.seconds != old->stoptime.seconds) { - put_format(b, " stoptime=%u:%02u", FRACTION(sample->stoptime.seconds, 60)); - old->stoptime = sample->stoptime; - } - - if (sample->stopdepth.mm != old->stopdepth.mm) { - put_milli(b, " stopdepth=", sample->stopdepth.mm, "m"); - old->stopdepth = sample->stopdepth; - } - - if (sample->cns != old->cns) { - put_format(b, " cns=%u%%", sample->cns); - old->cns = sample->cns; - } - - if (sample->rbt.seconds) - put_format(b, " rbt=%u:%02u", FRACTION(sample->rbt.seconds, 60)); - - if (sample->o2sensor[0].mbar != old->o2sensor[0].mbar) { - put_milli(b, " sensor1=", sample->o2sensor[0].mbar, "bar"); - old->o2sensor[0] = sample->o2sensor[0]; - } - - if ((sample->o2sensor[1].mbar) && (sample->o2sensor[1].mbar != old->o2sensor[1].mbar)) { - put_milli(b, " sensor2=", sample->o2sensor[1].mbar, "bar"); - old->o2sensor[1] = sample->o2sensor[1]; - } - - if ((sample->o2sensor[2].mbar) && (sample->o2sensor[2].mbar != old->o2sensor[2].mbar)) { - put_milli(b, " sensor3=", sample->o2sensor[2].mbar, "bar"); - old->o2sensor[2] = sample->o2sensor[2]; - } - - if (sample->setpoint.mbar != old->setpoint.mbar) { - put_milli(b, " po2=", sample->setpoint.mbar, "bar"); - old->setpoint = sample->setpoint; - } - show_index(b, sample->heartbeat, "heartbeat=", ""); - show_index(b, sample->bearing.degrees, "bearing=", "°"); - put_format(b, "\n"); -} - -static void save_samples(struct membuffer *b, int nr, struct sample *s) -{ - struct sample dummy = {}; - - while (--nr >= 0) { - save_sample(b, s, &dummy); - s++; - } -} - -static void save_one_event(struct membuffer *b, struct event *ev) -{ - put_format(b, "event %d:%02d", FRACTION(ev->time.seconds, 60)); - show_index(b, ev->type, "type=", ""); - show_index(b, ev->flags, "flags=", ""); - show_index(b, ev->value, "value=", ""); - show_utf8(b, " name=", ev->name, ""); - if (event_is_gaschange(ev)) { - if (ev->gas.index >= 0) { - show_index(b, ev->gas.index, "cylinder=", ""); - put_gasmix(b, &ev->gas.mix); - } else if (!event_gasmix_redundant(ev)) - put_gasmix(b, &ev->gas.mix); - } - put_string(b, "\n"); -} - -static void save_events(struct membuffer *b, struct event *ev) -{ - while (ev) { - save_one_event(b, ev); - ev = ev->next; - } -} - -static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer *dc) -{ - show_utf8(b, "model ", dc->model, "\n"); - if (dc->deviceid) - put_format(b, "deviceid %08x\n", dc->deviceid); - if (dc->diveid) - put_format(b, "diveid %08x\n", dc->diveid); - if (dc->when && dc->when != dive->when) - show_date(b, dc->when); - if (dc->duration.seconds && dc->duration.seconds != dive->dc.duration.seconds) - put_duration(b, dc->duration, "duration ", "min\n"); - if (dc->divemode != OC) { - put_format(b, "dctype %s\n", divemode_text[dc->divemode]); - put_format(b, "numberofoxygensensors %d\n",dc->no_o2sensors); - } - - save_depths(b, dc); - save_temperatures(b, dc); - save_airpressure(b, dc); - save_salinity(b, dc); - put_duration(b, dc->surfacetime, "surfacetime ", "min\n"); - - save_extra_data(b, dc->extra_data); - save_events(b, dc->events); - save_samples(b, dc->samples, dc->sample); -} - -/* - * Note that we don't save the date and time or dive - * number: they are encoded in the filename. - */ -static void create_dive_buffer(struct dive *dive, struct membuffer *b) -{ - put_format(b, "duration %u:%02u min\n", FRACTION(dive->dc.duration.seconds, 60)); - SAVE("rating", rating); - SAVE("visibility", visibility); - cond_put_format(dive->tripflag == NO_TRIP, b, "notrip\n"); - save_tags(b, dive->tag_list); - cond_put_format(dive->dive_site_uuid && get_dive_site_by_uuid(dive->dive_site_uuid), - b, "divesiteid %08x\n", dive->dive_site_uuid); - if (verbose && dive->dive_site_uuid && !get_dive_site_by_uuid(dive->dive_site_uuid)) - fprintf(stderr, "removed reference to non-existant dive site with uuid %08x\n", dive->dive_site_uuid); - save_overview(b, dive); - save_cylinder_info(b, dive); - save_weightsystem_info(b, dive); - save_dive_temperature(b, dive); -} - -static struct membuffer error_string_buffer = { 0 }; - -/* - * Note that the act of "getting" the error string - * buffer doesn't de-allocate the buffer, but it does - * set the buffer length to zero, so that any future - * error reports will overwrite the string rather than - * append to it. - */ -const char *get_error_string(void) -{ - const char *str; - - if (!error_string_buffer.len) - return ""; - str = mb_cstring(&error_string_buffer); - error_string_buffer.len = 0; - return str; -} - -int report_error(const char *fmt, ...) -{ - struct membuffer *buf = &error_string_buffer; - - /* Previous unprinted errors? Add a newline in between */ - if (buf->len) - put_bytes(buf, "\n", 1); - VA_BUF(buf, fmt); - mb_cstring(buf); - return -1; -} - -void report_message(const char *msg) -{ - (void)report_error("%s", msg); -} - -/* - * libgit2 has a "git_treebuilder" concept, but it's broken, and can not - * be used to do a flat tree (like the git "index") nor a recursive tree. - * Stupid. - * - * So we have to do that "keep track of recursive treebuilder entries" - * ourselves. We use 'git_treebuilder' for any regular files, and our own - * data structures for recursive trees. - * - * When finally writing it out, we traverse the subdirectories depth- - * first, writing them out, and then adding the written-out trees to - * the git_treebuilder they existed in. - */ -struct dir { - git_treebuilder *files; - struct dir *subdirs, *sibling; - char unique, name[1]; -}; - -static int tree_insert(git_treebuilder *dir, const char *name, int mkunique, git_oid *id, unsigned mode) -{ - int ret; - struct membuffer uniquename = { 0 }; - - if (mkunique && git_treebuilder_get(dir, name)) { - char hex[8]; - git_oid_nfmt(hex, 7, id); - hex[7] = 0; - put_format(&uniquename, "%s~%s", name, hex); - name = mb_cstring(&uniquename); - } - ret = git_treebuilder_insert(NULL, dir, name, id, mode); - free_buffer(&uniquename); - return ret; -} - -/* - * This does *not* make sure the new subdirectory doesn't - * alias some existing name. That is actually useful: you - * can create multiple directories with the same name, and - * set the "unique" flag, which will then append the SHA1 - * of the directory to the name when it is written. - */ -static struct dir *new_directory(git_repository *repo, struct dir *parent, struct membuffer *namebuf) -{ - struct dir *subdir; - const char *name = mb_cstring(namebuf); - int len = namebuf->len; - - subdir = malloc(sizeof(*subdir)+len); - - /* - * It starts out empty: no subdirectories of its own, - * and an empty treebuilder list of files. - */ - subdir->subdirs = NULL; - git_treebuilder_new(&subdir->files, repo, NULL); - memcpy(subdir->name, name, len); - subdir->unique = 0; - subdir->name[len] = 0; - - /* Add it to the list of subdirs of the parent */ - subdir->sibling = parent->subdirs; - parent->subdirs = subdir; - - return subdir; -} - -static struct dir *mktree(git_repository *repo, struct dir *dir, const char *fmt, ...) -{ - struct membuffer buf = { 0 }; - struct dir *subdir; - - VA_BUF(&buf, fmt); - for (subdir = dir->subdirs; subdir; subdir = subdir->sibling) { - if (subdir->unique) - continue; - if (strncmp(subdir->name, buf.buffer, buf.len)) - continue; - if (!subdir->name[buf.len]) - break; - } - if (!subdir) - subdir = new_directory(repo, dir, &buf); - free_buffer(&buf); - return subdir; -} - -/* - * The name of a dive is the date and the dive number (and possibly - * the uniqueness suffix). - * - * Note that the time of the dive may not be the same as the - * time of the directory structure it is created in: the dive - * might be part of a trip that straddles a month (or even a - * year). - * - * We do *not* want to use localized weekdays and cause peoples save - * formats to depend on their locale. - */ -static void create_dive_name(struct dive *dive, struct membuffer *name, struct tm *dirtm) -{ - struct tm tm; - static const char weekday[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - - utc_mkdate(dive->when, &tm); - if (tm.tm_year != dirtm->tm_year) - put_format(name, "%04u-", tm.tm_year + 1900); - if (tm.tm_mon != dirtm->tm_mon) - put_format(name, "%02u-", tm.tm_mon+1); - - /* a colon is an illegal char in a file name on Windows - use an '=' instead */ - put_format(name, "%02u-%s-%02u=%02u=%02u", - tm.tm_mday, weekday[tm.tm_wday], - tm.tm_hour, tm.tm_min, tm.tm_sec); -} - -/* - * Write a membuffer to the git repo, and free it - */ -static int blob_insert(git_repository *repo, struct dir *tree, struct membuffer *b, const char *fmt, ...) -{ - int ret; - git_oid blob_id; - struct membuffer name = { 0 }; - - ret = git_blob_create_frombuffer(&blob_id, repo, b->buffer, b->len); - free_buffer(b); - if (ret) - return ret; - - VA_BUF(&name, fmt); - ret = tree_insert(tree->files, mb_cstring(&name), 1, &blob_id, GIT_FILEMODE_BLOB); - free_buffer(&name); - return ret; -} - -static int save_one_divecomputer(git_repository *repo, struct dir *tree, struct dive *dive, struct divecomputer *dc, int idx) -{ - int ret; - struct membuffer buf = { 0 }; - - save_dc(&buf, dive, dc); - ret = blob_insert(repo, tree, &buf, "Divecomputer%c%03u", idx ? '-' : 0, idx); - if (ret) - report_error("divecomputer tree insert failed"); - return ret; -} - -static int save_one_picture(git_repository *repo, struct dir *dir, struct picture *pic) -{ - int offset = pic->offset.seconds; - struct membuffer buf = { 0 }; - char sign = '+'; - unsigned h; - int error; - - 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) { - offset = -offset; - sign = '-'; - } - - /* Use full hh:mm:ss format to make it all sort nicely */ - h = offset / 3600; - offset -= h *3600; - error = blob_insert(repo, dir, &buf, "%c%02u=%02u=%02u", - sign, h, FRACTION(offset, 60)); -#if 0 - /* storing pictures into git was a mistake. This makes for HUGE git repositories */ - if (!error) { - /* next store the actual picture; we prefix all picture names - * with "PIC-" to make things easier on the parsing side */ - struct membuffer namebuf = { 0 }; - const char *localfn = local_file_path(pic); - put_format(&namebuf, "PIC-%s", pic->hash); - error = blob_insert_fromdisk(repo, dir, localfn, mb_cstring(&namebuf)); - free((void *)localfn); - } -#endif - return error; -} - -static int save_pictures(git_repository *repo, struct dir *dir, struct dive *dive) -{ - if (dive->picture_list) { - dir = mktree(repo, dir, "Pictures"); - FOR_EACH_PICTURE(dive) { - save_one_picture(repo, dir, picture); - } - } - return 0; -} - -static int save_one_dive(git_repository *repo, struct dir *tree, struct dive *dive, struct tm *tm, bool cached_ok) -{ - struct divecomputer *dc; - struct membuffer buf = { 0 }, name = { 0 }; - struct dir *subdir; - int ret, nr; - - /* Create dive directory */ - create_dive_name(dive, &name, tm); - - /* - * If the dive git ID is valid, we just create the whole directory - * with that ID - */ - if (cached_ok && dive_cache_is_valid(dive)) { - git_oid oid; - git_oid_fromraw(&oid, dive->git_id); - ret = tree_insert(tree->files, mb_cstring(&name), 1, - &oid, GIT_FILEMODE_TREE); - free_buffer(&name); - if (ret) - return report_error("cached dive tree insert failed"); - return 0; - } - - subdir = new_directory(repo, tree, &name); - subdir->unique = 1; - free_buffer(&name); - - create_dive_buffer(dive, &buf); - nr = dive->number; - ret = blob_insert(repo, subdir, &buf, - "Dive%c%d", nr ? '-' : 0, nr); - if (ret) - return report_error("dive save-file tree insert failed"); - - /* - * Save the dive computer data. If there is only one dive - * computer, use index 0 for that (which disables the index - * generation when naming it). - */ - dc = &dive->dc; - nr = dc->next ? 1 : 0; - do { - save_one_divecomputer(repo, subdir, dive, dc, nr++); - dc = dc->next; - } while (dc); - - /* Save the picture data, if any */ - save_pictures(repo, subdir, dive); - return 0; -} - -/* - * We'll mark the trip directories unique, so this does not - * need to be unique per se. It could be just "trip". But - * to make things a bit more user-friendly, we try to take - * the trip location into account. - * - * But no special characters, and no numbers (numbers in the - * name could be construed as a date). - * - * So we might end up with "02-Maui", and then the unique - * flag will make us write it out as "02-Maui~54b4" or - * similar. - */ -#define MAXTRIPNAME 15 -static void create_trip_name(dive_trip_t *trip, struct membuffer *name, struct tm *tm) -{ - put_format(name, "%02u-", tm->tm_mday); - if (trip->location) { - char ascii_loc[MAXTRIPNAME+1], *p = trip->location; - int i; - - for (i = 0; i < MAXTRIPNAME; ) { - char c = *p++; - switch (c) { - case 0: - case ',': - case '.': - break; - - case 'a' ... 'z': - case 'A' ... 'Z': - ascii_loc[i++] = c; - continue; - default: - continue; - } - break; - } - if (i > 1) { - put_bytes(name, ascii_loc, i); - return; - } - } - - /* No useful name? */ - put_string(name, "trip"); -} - -static int save_trip_description(git_repository *repo, struct dir *dir, dive_trip_t *trip, struct tm *tm) -{ - int ret; - git_oid blob_id; - struct membuffer desc = { 0 }; - - put_format(&desc, "date %04u-%02u-%02u\n", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); - put_format(&desc, "time %02u:%02u:%02u\n", - tm->tm_hour, tm->tm_min, tm->tm_sec); - - show_utf8(&desc, "location ", trip->location, "\n"); - show_utf8(&desc, "notes ", trip->notes, "\n"); - - ret = git_blob_create_frombuffer(&blob_id, repo, desc.buffer, desc.len); - free_buffer(&desc); - if (ret) - return report_error("trip blob creation failed"); - ret = tree_insert(dir->files, "00-Trip", 0, &blob_id, GIT_FILEMODE_BLOB); - if (ret) - return report_error("trip description tree insert failed"); - return 0; -} - -static void verify_shared_date(timestamp_t when, struct tm *tm) -{ - struct tm tmp_tm; - - utc_mkdate(when, &tmp_tm); - if (tmp_tm.tm_year != tm->tm_year) { - tm->tm_year = -1; - tm->tm_mon = -1; - } - if (tmp_tm.tm_mon != tm->tm_mon) - tm->tm_mon = -1; -} - -#define MIN_TIMESTAMP (0) -#define MAX_TIMESTAMP (0x7fffffffffffffff) - -static int save_one_trip(git_repository *repo, struct dir *tree, dive_trip_t *trip, struct tm *tm, bool cached_ok) -{ - int i; - struct dive *dive; - struct dir *subdir; - struct membuffer name = { 0 }; - timestamp_t first, last; - - /* Create trip directory */ - create_trip_name(trip, &name, tm); - subdir = new_directory(repo, tree, &name); - subdir->unique = 1; - free_buffer(&name); - - /* Trip description file */ - save_trip_description(repo, subdir, trip, tm); - - /* Make sure we write out the dates to the dives consistently */ - first = MAX_TIMESTAMP; - last = MIN_TIMESTAMP; - for_each_dive(i, dive) { - if (dive->divetrip != trip) - continue; - if (dive->when < first) - first = dive->when; - if (dive->when > last) - last = dive->when; - } - verify_shared_date(first, tm); - verify_shared_date(last, tm); - - /* Save each dive in the directory */ - for_each_dive(i, dive) { - if (dive->divetrip == trip) - save_one_dive(repo, subdir, dive, tm, cached_ok); - } - - return 0; -} - -static void save_units(void *_b) -{ - struct membuffer *b =_b; - if (prefs.unit_system == METRIC) - put_string(b, "units METRIC\n"); - else if (prefs.unit_system == IMPERIAL) - put_string(b, "units IMPERIAL\n"); - else - put_format(b, "units PERSONALIZE %s %s %s %s %s %s", - prefs.units.length == METERS ? "METERS" : "FEET", - prefs.units.volume == LITER ? "LITER" : "CUFT", - prefs.units.pressure == BAR ? "BAR" : prefs.units.pressure == PSI ? "PSI" : "PASCAL", - prefs.units.temperature == CELSIUS ? "CELSIUS" : prefs.units.temperature == FAHRENHEIT ? "FAHRENHEIT" : "KELVIN", - prefs.units.weight == KG ? "KG" : "LBS", - prefs.units.vertical_speed_time == SECONDS ? "SECONDS" : "MINUTES"); -} - -static void save_userid(void *_b) -{ - struct membuffer *b = _b; - if (prefs.save_userid_local) - put_format(b, "userid %30s\n", prefs.userid); -} - -static void save_one_device(void *_b, const char *model, uint32_t deviceid, - const char *nickname, const char *serial, const char *firmware) -{ - struct membuffer *b = _b; - - if (nickname && !strcmp(model, nickname)) - nickname = NULL; - if (serial && !*serial) serial = NULL; - if (firmware && !*firmware) firmware = NULL; - if (nickname && !*nickname) nickname = NULL; - if (!nickname && !serial && !firmware) - return; - - show_utf8(b, "divecomputerid ", model, ""); - put_format(b, " deviceid=%08x", deviceid); - show_utf8(b, " serial=", serial, ""); - show_utf8(b, " firmware=", firmware, ""); - show_utf8(b, " nickname=", nickname, ""); - put_string(b, "\n"); -} - -static void save_settings(git_repository *repo, struct dir *tree) -{ - struct membuffer b = { 0 }; - - put_format(&b, "version %d\n", DATAFORMAT_VERSION); - save_userid(&b); - call_for_each_dc(&b, save_one_device, false); - cond_put_format(autogroup, &b, "autogroup\n"); - save_units(&b); - - blob_insert(repo, tree, &b, "00-Subsurface"); -} - -static void save_divesites(git_repository *repo, struct dir *tree) -{ - struct dir *subdir; - struct membuffer dirname = { 0 }; - put_format(&dirname, "01-Divesites"); - subdir = new_directory(repo, tree, &dirname); - - for (int i = 0; i < dive_site_table.nr; i++) { - struct membuffer b = { 0 }; - struct dive_site *ds = get_dive_site(i); - if (dive_site_is_empty(ds)) { - int j; - struct dive *d; - for_each_dive(j, d) { - if (d->dive_site_uuid == ds->uuid) - d->dive_site_uuid = 0; - } - delete_dive_site(ds->uuid); - i--; // since we just deleted that one - continue; - } else if (ds->name && - (strncmp(ds->name, "Auto-created dive", 17) == 0 || - strncmp(ds->name, "New Dive", 8) == 0)) { - fprintf(stderr, "found an auto divesite %s\n", ds->name); - // these are the two default names for sites from - // the web service; if the site isn't used in any - // dive (really? you didn't rename it?), delete it - if (!is_dive_site_used(ds->uuid, false)) { - if (verbose) - fprintf(stderr, "Deleted unused auto-created dive site %s\n", ds->name); - delete_dive_site(ds->uuid); - i--; // since we just deleted that one - continue; - } - } - struct membuffer site_file_name = { 0 }; - put_format(&site_file_name, "Site-%08x", ds->uuid); - show_utf8(&b, "name ", ds->name, "\n"); - show_utf8(&b, "description ", ds->description, "\n"); - show_utf8(&b, "notes ", ds->notes, "\n"); - show_gps(&b, ds->latitude, ds->longitude); - for (int j = 0; j < ds->taxonomy.nr; j++) { - struct taxonomy *t = &ds->taxonomy.category[j]; - if (t->category != TC_NONE && t->value) { - put_format(&b, "geo cat %d origin %d ", t->category, t->origin); - show_utf8(&b, "", t->value, "\n" ); - } - } - blob_insert(repo, subdir, &b, mb_cstring(&site_file_name)); - } -} - -static int create_git_tree(git_repository *repo, struct dir *root, bool select_only, bool cached_ok) -{ - int i; - struct dive *dive; - dive_trip_t *trip; - - save_settings(repo, root); - - save_divesites(repo, root); - - for (trip = dive_trip_list; trip != NULL; trip = trip->next) - trip->index = 0; - - /* save the dives */ - int notify_increment = dive_table.nr > 10 ? dive_table.nr / 10 : 1; - int last_threshold = 0; - for_each_dive(i, dive) { - struct tm tm; - struct dir *tree; - char buf[] = "save dives x0%"; - - if (i / notify_increment > last_threshold) { - // notify of progress - we cover the range of 20..50 - last_threshold = i / notify_increment; - buf[11] = last_threshold + '0'; - git_storage_update_progress(20 + 3 * last_threshold, buf); - } - - trip = dive->divetrip; - - if (select_only) { - if (!dive->selected) - continue; - /* We don't save trips when doing selected dive saves */ - trip = NULL; - } - - /* Create the date-based hierarchy */ - utc_mkdate(trip ? trip->when : dive->when, &tm); - tree = mktree(repo, root, "%04d", tm.tm_year + 1900); - tree = mktree(repo, tree, "%02d", tm.tm_mon + 1); - - if (trip) { - /* Did we already save this trip? */ - if (trip->index) - continue; - trip->index = 1; - - /* Pass that new subdirectory in for save-trip */ - save_one_trip(repo, tree, trip, &tm, cached_ok); - continue; - } - - save_one_dive(repo, tree, dive, &tm, cached_ok); - } - return 0; -} - -/* - * See if we can find the parent ID that the git data came from - */ -static git_object *try_to_find_parent(const char *hex_id, git_repository *repo) -{ - git_oid object_id; - git_commit *commit; - - if (!hex_id) - return NULL; - if (git_oid_fromstr(&object_id, hex_id)) - return NULL; - if (git_commit_lookup(&commit, repo, &object_id)) - return NULL; - return (git_object *)commit; -} - -static int notify_cb(git_checkout_notify_t why, - const char *path, - const git_diff_file *baseline, - const git_diff_file *target, - const git_diff_file *workdir, - void *payload) -{ - (void) baseline; - (void) target; - (void) workdir; - (void) payload; - (void) why; - report_error("File '%s' does not match in working tree", path); - return 0; /* Continue with checkout */ -} - -static git_tree *get_git_tree(git_repository *repo, git_object *parent) -{ - git_tree *tree; - if (!parent) - return NULL; - if (git_tree_lookup(&tree, repo, git_commit_tree_id((const git_commit *) parent))) - return NULL; - return tree; -} - -int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_DIRTY; - opts.notify_cb = notify_cb; - opts.baseline = get_git_tree(repo, parent); - return git_checkout_tree(repo, (git_object *) tree, &opts); -} - -static int get_authorship(git_repository *repo, git_signature **authorp) -{ -#if LIBGIT2_VER_MAJOR || LIBGIT2_VER_MINOR >= 20 - if (git_signature_default(authorp, repo) == 0) - return 0; -#endif - /* Default name information, with potential OS overrides */ - struct user_info user = { - .name = "Subsurface", - .email = "subsurace@subsurface-divelog.org" - }; - - subsurface_user_info(&user); - - /* git_signature_default() is too recent */ - return git_signature_now(authorp, user.name, user.email); -} - -static void create_commit_message(struct membuffer *msg) -{ - int nr = dive_table.nr; - struct dive *dive = get_dive(nr-1); - - if (dive) { - dive_trip_t *trip = dive->divetrip; - const char *location = get_dive_location(dive) ? : "no location"; - struct divecomputer *dc = &dive->dc; - const char *sep = "\n"; - - if (dive->number) - nr = dive->number; - - put_format(msg, "dive %d: %s", nr, location); - if (trip && trip->location && *trip->location && strcmp(trip->location, location)) - put_format(msg, " (%s)", trip->location); - put_format(msg, "\n"); - do { - if (dc->model && *dc->model) { - put_format(msg, "%s%s", sep, dc->model); - sep = ", "; - } - } while ((dc = dc->next) != NULL); - put_format(msg, "\n"); - } - put_format(msg, "Created by subsurface %s\n", subsurface_user_agent()); -} - -static int create_new_commit(git_repository *repo, const char *remote, const char *branch, git_oid *tree_id) -{ - int ret; - git_reference *ref; - git_object *parent; - git_oid commit_id; - git_signature *author; - git_commit *commit; - git_tree *tree; - - ret = git_branch_lookup(&ref, repo, branch, GIT_BRANCH_LOCAL); - switch (ret) { - default: - return report_error("Bad branch '%s' (%s)", branch, strerror(errno)); - case GIT_EINVALIDSPEC: - return report_error("Invalid branch name '%s'", branch); - case GIT_ENOTFOUND: /* We'll happily create it */ - ref = NULL; - parent = try_to_find_parent(saved_git_id, repo); - break; - case 0: - if (git_reference_peel(&parent, ref, GIT_OBJ_COMMIT)) - return report_error("Unable to look up parent in branch '%s'", branch); - - if (saved_git_id) { - if (existing_filename) - fprintf(stderr, "existing filename %s\n", existing_filename); - const git_oid *id = git_commit_id((const git_commit *) parent); - /* if we are saving to the same git tree we got this from, let's make - * sure there is no confusion */ - if (same_string(existing_filename, remote) && git_oid_strcmp(id, saved_git_id)) - return report_error("The git branch does not match the git parent of the source"); - } - - /* all good */ - break; - } - - if (git_tree_lookup(&tree, repo, tree_id)) - return report_error("Could not look up newly created tree"); - - if (get_authorship(repo, &author)) - return report_error("No user name configuration in git repo"); - - /* If the parent commit has the same tree ID, do not create a new commit */ - if (parent && git_oid_equal(tree_id, git_commit_tree_id((const git_commit *) parent))) { - /* If the parent already came from the ref, the commit is already there */ - if (ref) - return 0; - /* Else we do want to create the new branch, but with the old commit */ - commit = (git_commit *) parent; - } else { - struct membuffer commit_msg = { 0 }; - - create_commit_message(&commit_msg); - if (git_commit_create_v(&commit_id, repo, NULL, author, author, NULL, mb_cstring(&commit_msg), tree, parent != NULL, parent)) - return report_error("Git commit create failed (%s)", strerror(errno)); - free_buffer(&commit_msg); - - if (git_commit_lookup(&commit, repo, &commit_id)) - return report_error("Could not look up newly created commit"); - } - - if (!ref) { - if (git_branch_create(&ref, repo, branch, commit, 0)) - return report_error("Failed to create branch '%s'", branch); - } - /* - * If it's a checked-out branch, try to also update the working - * tree and index. If that fails (dirty working tree or whatever), - * this is not technically a save error (we did save things in - * the object database), but it can cause extreme confusion, so - * warn about it. - */ - if (git_branch_is_head(ref) && !git_repository_is_bare(repo)) { - if (update_git_checkout(repo, parent, tree)) { - const git_error *err = giterr_last(); - const char *errstr = err ? err->message : strerror(errno); - report_error("Git branch '%s' is checked out, but worktree is dirty (%s)", - branch, errstr); - } - } - - if (git_reference_set_target(&ref, ref, &commit_id, "Subsurface save event")) - return report_error("Failed to update branch '%s'", branch); - set_git_id(&commit_id); - - git_signature_free(author); - - return 0; -} - -static int write_git_tree(git_repository *repo, struct dir *tree, git_oid *result) -{ - int ret; - struct dir *subdir; - - /* Write out our subdirectories, add them to the treebuilder, and free them */ - while ((subdir = tree->subdirs) != NULL) { - git_oid id; - - if (!write_git_tree(repo, subdir, &id)) - tree_insert(tree->files, subdir->name, subdir->unique, &id, GIT_FILEMODE_TREE); - tree->subdirs = subdir->sibling; - free(subdir); - }; - - /* .. write out the resulting treebuilder */ - ret = git_treebuilder_write(result, tree->files); - - /* .. and free the now useless treebuilder */ - git_treebuilder_free(tree->files); - - return ret; -} - -int do_git_save(git_repository *repo, const char *branch, const char *remote, bool select_only, bool create_empty) -{ - struct dir tree; - git_oid id; - bool cached_ok; - - if (verbose) - fprintf(stderr, "git storage: do git save\n"); - - if (!create_empty) // so we are actually saving the dives - git_storage_update_progress(19, "start git save"); - - /* - * Check if we can do the cached writes - we need to - * have the original git commit we loaded in the repo - */ - cached_ok = try_to_find_parent(saved_git_id, repo); - - /* Start with an empty tree: no subdirectories, no files */ - tree.name[0] = 0; - tree.subdirs = NULL; - if (git_treebuilder_new(&tree.files, repo, NULL)) - return report_error("git treebuilder failed"); - - if (!create_empty) - /* Populate our tree data structure */ - if (create_git_tree(repo, &tree, select_only, cached_ok)) - return -1; - - if (verbose) - fprintf(stderr, "git storage, write git tree\n"); - - if (write_git_tree(repo, &tree, &id)) - return report_error("git tree write failed"); - - /* And save the tree! */ - if (create_new_commit(repo, remote, branch, &id)) - return report_error("creating commit failed"); - - if (remote && prefs.cloud_background_sync) { - /* now sync the tree with the cloud server */ - if (strstr(remote, prefs.cloud_git_url)) { - return sync_with_remote(repo, remote, branch, RT_HTTPS); - } - } - return 0; -} - -int git_save_dives(struct git_repository *repo, const char *branch, const char *remote, bool select_only) -{ - int ret; - - if (repo == dummy_git_repository) - return report_error("Unable to open git repository '%s'", branch); - ret = do_git_save(repo, branch, remote, select_only, false); - git_repository_free(repo); - free((void *)branch); - return ret; -} diff --git a/subsurface-core/save-html.c b/subsurface-core/save-html.c deleted file mode 100644 index 2d0ea9cf3..000000000 --- a/subsurface-core/save-html.c +++ /dev/null @@ -1,559 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include "save-html.h" -#include "qthelperfromc.h" -#include "gettext.h" -#include "stdio.h" - -void write_attribute(struct membuffer *b, const char *att_name, const char *value, const char *separator) -{ - if (!value) - value = "--"; - put_format(b, "\"%s\":\"", att_name); - put_HTML_quoted(b, value); - put_format(b, "\"%s", separator); -} - -void save_photos(struct membuffer *b, const char *photos_dir, struct dive *dive) -{ - struct picture *pic = dive->picture_list; - - if (!pic) - return; - - char *separator = "\"photos\":["; - do { - put_string(b, separator); - separator = ", "; - char *fname = get_file_name(local_file_path(pic)); - put_format(b, "{\"filename\":\"%s\"}", fname); - copy_image_and_overwrite(local_file_path(pic), photos_dir, fname); - free(fname); - pic = pic->next; - } while (pic); - put_string(b, "],"); -} - -void write_divecomputers(struct membuffer *b, struct dive *dive) -{ - put_string(b, "\"divecomputers\":["); - struct divecomputer *dc; - char *separator = ""; - for_each_dc (dive, dc) { - put_string(b, separator); - separator = ", "; - put_format(b, "{"); - write_attribute(b, "model", dc->model, ", "); - if (dc->deviceid) - put_format(b, "\"deviceid\":\"%08x\", ", dc->deviceid); - else - put_string(b, "\"deviceid\":\"--\", "); - if (dc->diveid) - put_format(b, "\"diveid\":\"%08x\" ", dc->diveid); - else - put_string(b, "\"diveid\":\"--\" "); - put_format(b, "}"); - } - put_string(b, "],"); -} - -void write_dive_status(struct membuffer *b, struct dive *dive) -{ - put_format(b, "\"sac\":\"%d\",", dive->sac); - put_format(b, "\"otu\":\"%d\",", dive->otu); - put_format(b, "\"cns\":\"%d\",", dive->cns); -} - -void put_HTML_bookmarks(struct membuffer *b, struct dive *dive) -{ - struct event *ev = dive->dc.events; - - if (!ev) - return; - - char *separator = "\"events\":["; - do { - put_string(b, separator); - separator = ", "; - put_format(b, "{\"name\":\"%s\",", ev->name); - put_format(b, "\"value\":\"%d\",", ev->value); - put_format(b, "\"type\":\"%d\",", ev->type); - put_format(b, "\"time\":\"%d\"}", ev->time.seconds); - ev = ev->next; - } while (ev); - put_string(b, "],"); -} - -static void put_weightsystem_HTML(struct membuffer *b, struct dive *dive) -{ - int i, nr; - - nr = nr_weightsystems(dive); - - put_string(b, "\"Weights\":["); - - char *separator = ""; - - for (i = 0; i < nr; i++) { - weightsystem_t *ws = dive->weightsystem + i; - int grams = ws->weight.grams; - const char *description = ws->description; - - put_string(b, separator); - separator = ", "; - put_string(b, "{"); - put_HTML_weight_units(b, grams, "\"weight\":\"", "\","); - write_attribute(b, "description", description, " "); - put_string(b, "}"); - } - put_string(b, "],"); -} - -static void put_cylinder_HTML(struct membuffer *b, struct dive *dive) -{ - int i, nr; - char *separator = "\"Cylinders\":["; - nr = nr_cylinders(dive); - - if (!nr) - put_string(b, separator); - - for (i = 0; i < nr; i++) { - cylinder_t *cylinder = dive->cylinder + i; - put_format(b, "%s{", separator); - separator = ", "; - write_attribute(b, "Type", cylinder->type.description, ", "); - if (cylinder->type.size.mliter) { - int volume = cylinder->type.size.mliter; - if (prefs.units.volume == CUFT && cylinder->type.workingpressure.mbar) - volume *= bar_to_atm(cylinder->type.workingpressure.mbar / 1000.0); - put_HTML_volume_units(b, volume, "\"Size\":\"", " \", "); - } else { - write_attribute(b, "Size", "--", ", "); - } - put_HTML_pressure_units(b, cylinder->type.workingpressure, "\"WPressure\":\"", " \", "); - - if (cylinder->start.mbar) { - put_HTML_pressure_units(b, cylinder->start, "\"SPressure\":\"", " \", "); - } else { - write_attribute(b, "SPressure", "--", ", "); - } - - if (cylinder->end.mbar) { - put_HTML_pressure_units(b, cylinder->end, "\"EPressure\":\"", " \", "); - } else { - write_attribute(b, "EPressure", "--", ", "); - } - - if (cylinder->gasmix.o2.permille) { - put_format(b, "\"O2\":\"%u.%u%%\",", FRACTION(cylinder->gasmix.o2.permille, 10)); - put_format(b, "\"He\":\"%u.%u%%\"", FRACTION(cylinder->gasmix.he.permille, 10)); - } else { - write_attribute(b, "O2", "Air", ""); - } - - put_string(b, "}"); - } - - put_string(b, "],"); -} - - -void put_HTML_samples(struct membuffer *b, struct dive *dive) -{ - int i; - put_format(b, "\"maxdepth\":%d,", dive->dc.maxdepth.mm); - put_format(b, "\"duration\":%d,", dive->dc.duration.seconds); - struct sample *s = dive->dc.sample; - - if (!dive->dc.samples) - return; - - char *separator = "\"samples\":["; - for (i = 0; i < dive->dc.samples; i++) { - put_format(b, "%s[%d,%d,%d,%d]", separator, s->time.seconds, s->depth.mm, s->cylinderpressure.mbar, s->temperature.mkelvin); - separator = ", "; - s++; - } - put_string(b, "],"); -} - -void put_HTML_coordinates(struct membuffer *b, struct dive *dive) -{ - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds) - return; - degrees_t latitude = ds->latitude; - degrees_t longitude = ds->longitude; - - //don't put coordinates if in (0,0) - if (!latitude.udeg && !longitude.udeg) - return; - - put_string(b, "\"coordinates\":{"); - put_degrees(b, latitude, "\"lat\":\"", "\","); - put_degrees(b, longitude, "\"lon\":\"", "\""); - put_string(b, "},"); -} - -void put_HTML_date(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - struct tm tm; - utc_mkdate(dive->when, &tm); - put_format(b, "%s%04u-%02u-%02u%s", pre, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, post); -} - -void put_HTML_quoted(struct membuffer *b, const char *text) -{ - int is_html = 1, is_attribute = 1; - put_quoted(b, text, is_attribute, is_html); -} - -void put_HTML_notes(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - put_string(b, pre); - if (dive->notes) { - put_HTML_quoted(b, dive->notes); - } else { - put_string(b, "--"); - } - put_string(b, post); -} - -void put_HTML_pressure_units(struct membuffer *b, pressure_t pressure, const char *pre, const char *post) -{ - const char *unit; - double value; - - if (!pressure.mbar) { - put_format(b, "%s%s", pre, post); - return; - } - - value = get_pressure_units(pressure.mbar, &unit); - put_format(b, "%s%.1f %s%s", pre, value, unit, post); -} - -void put_HTML_volume_units(struct membuffer *b, unsigned int ml, const char *pre, const char *post) -{ - const char *unit; - double value; - int frac; - - value = get_volume_units(ml, &frac, &unit); - put_format(b, "%s%.1f %s%s", pre, value, unit, post); -} - -void put_HTML_weight_units(struct membuffer *b, unsigned int grams, const char *pre, const char *post) -{ - const char *unit; - double value; - int frac; - - value = get_weight_units(grams, &frac, &unit); - put_format(b, "%s%.1f %s%s", pre, value, unit, post); -} - -void put_HTML_time(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - struct tm tm; - utc_mkdate(dive->when, &tm); - put_format(b, "%s%02u:%02u:%02u%s", pre, tm.tm_hour, tm.tm_min, tm.tm_sec, post); -} - -void put_HTML_depth(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - const char *unit; - double value; - struct units *units_p = get_units(); - - if (!dive->maxdepth.mm) { - put_format(b, "%s--%s", pre, post); - return; - } - value = get_depth_units(dive->maxdepth.mm, NULL, &unit); - - switch (units_p->length) { - case METERS: - default: - put_format(b, "%s%.1f %s%s", pre, value, unit, post); - break; - case FEET: - put_format(b, "%s%.0f %s%s", pre, value, unit, post); - break; - } -} - -void put_HTML_airtemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - const char *unit; - double value; - - if (!dive->airtemp.mkelvin) { - put_format(b, "%s--%s", pre, post); - return; - } - value = get_temp_units(dive->airtemp.mkelvin, &unit); - put_format(b, "%s%.1f %s%s", pre, value, unit, post); -} - -void put_HTML_watertemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - const char *unit; - double value; - - if (!dive->watertemp.mkelvin) { - put_format(b, "%s--%s", pre, post); - return; - } - value = get_temp_units(dive->watertemp.mkelvin, &unit); - put_format(b, "%s%.1f %s%s", pre, value, unit, post); -} - -void put_HTML_tags(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - put_string(b, pre); - struct tag_entry *tag = dive->tag_list; - - if (!tag) - put_string(b, "[\"--\""); - - char *separator = "["; - while (tag) { - put_format(b, "%s\"", separator); - separator = ", "; - put_HTML_quoted(b, tag->tag->name); - put_string(b, "\""); - tag = tag->next; - } - put_string(b, "]"); - put_string(b, post); -} - -/* if exporting list_only mode, we neglect exporting the samples, bookmarks and cylinders */ -void write_one_dive(struct membuffer *b, struct dive *dive, const char *photos_dir, int *dive_no, const bool list_only) -{ - put_string(b, "{"); - put_format(b, "\"number\":%d,", *dive_no); - put_format(b, "\"subsurface_number\":%d,", dive->number); - put_HTML_date(b, dive, "\"date\":\"", "\","); - put_HTML_time(b, dive, "\"time\":\"", "\","); - write_attribute(b, "location", get_dive_location(dive), ", "); - put_HTML_coordinates(b, dive); - put_format(b, "\"rating\":%d,", dive->rating); - put_format(b, "\"visibility\":%d,", dive->visibility); - put_format(b, "\"dive_duration\":\"%u:%02u min\",", - FRACTION(dive->duration.seconds, 60)); - put_string(b, "\"temperature\":{"); - put_HTML_airtemp(b, dive, "\"air\":\"", "\","); - put_HTML_watertemp(b, dive, "\"water\":\"", "\""); - put_string(b, " },"); - write_attribute(b, "buddy", dive->buddy, ", "); - write_attribute(b, "divemaster", dive->divemaster, ", "); - write_attribute(b, "suit", dive->suit, ", "); - put_HTML_tags(b, dive, "\"tags\":", ","); - if (!list_only) { - put_cylinder_HTML(b, dive); - put_weightsystem_HTML(b, dive); - put_HTML_samples(b, dive); - put_HTML_bookmarks(b, dive); - write_dive_status(b, dive); - if (photos_dir && strcmp(photos_dir, "")) - save_photos(b, photos_dir, dive); - write_divecomputers(b, dive); - } - put_HTML_notes(b, dive, "\"notes\":\"", "\""); - put_string(b, "}\n"); - (*dive_no)++; -} - -void write_no_trip(struct membuffer *b, int *dive_no, bool selected_only, const char *photos_dir, const bool list_only, char *sep) -{ - int i; - struct dive *dive; - char *separator = ""; - bool found_sel_dive = 0; - - for_each_dive (i, dive) { - // write dive if it doesn't belong to any trip and the dive is selected - // or we are in exporting all dives mode. - if (!dive->divetrip && (dive->selected || !selected_only)) { - if (!found_sel_dive) { - put_format(b, "%c{", *sep); - (*sep) = ','; - put_format(b, "\"name\":\"Other\","); - put_format(b, "\"dives\":["); - found_sel_dive = 1; - } - put_string(b, separator); - separator = ", "; - write_one_dive(b, dive, photos_dir, dive_no, list_only); - } - } - if (found_sel_dive) - put_format(b, "]}\n\n"); -} - -void write_trip(struct membuffer *b, dive_trip_t *trip, int *dive_no, bool selected_only, const char *photos_dir, const bool list_only, char *sep) -{ - struct dive *dive; - char *separator = ""; - bool found_sel_dive = 0; - - for (dive = trip->dives; dive != NULL; dive = dive->next) { - if (!dive->selected && selected_only) - continue; - - // save trip if found at least one selected dive. - if (!found_sel_dive) { - found_sel_dive = 1; - put_format(b, "%c {", *sep); - (*sep) = ','; - put_format(b, "\"name\":\"%s\",", trip->location); - put_format(b, "\"dives\":["); - } - put_string(b, separator); - separator = ", "; - write_one_dive(b, dive, photos_dir, dive_no, list_only); - } - - // close the trip object if contain dives. - if (found_sel_dive) - put_format(b, "]}\n\n"); -} - -void write_trips(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only) -{ - int i, dive_no = 0; - struct dive *dive; - dive_trip_t *trip; - char sep_ = ' '; - char *sep = &sep_; - - for (trip = dive_trip_list; trip != NULL; trip = trip->next) - trip->index = 0; - - for_each_dive (i, dive) { - trip = dive->divetrip; - - /*Continue if the dive have no trips or we have seen this trip before*/ - if (!trip || trip->index) - continue; - - /* We haven't seen this trip before - save it and all dives */ - trip->index = 1; - write_trip(b, trip, &dive_no, selected_only, photos_dir, list_only, sep); - } - - /*Save all remaining trips into Others*/ - write_no_trip(b, &dive_no, selected_only, photos_dir, list_only, sep); -} - -void export_list(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only) -{ - put_string(b, "trips=["); - write_trips(b, photos_dir, selected_only, list_only); - put_string(b, "]"); -} - -void export_HTML(const char *file_name, const char *photos_dir, const bool selected_only, const bool list_only) -{ - FILE *f; - - struct membuffer buf = { 0 }; - export_list(&buf, photos_dir, selected_only, list_only); - - f = subsurface_fopen(file_name, "w+"); - if (!f) { - report_error(translate("gettextFromC", "Can't open file %s"), file_name); - } else { - flush_buffer(&buf, f); /*check for writing errors? */ - fclose(f); - } - free_buffer(&buf); -} - -void export_translation(const char *file_name) -{ - FILE *f; - - struct membuffer buf = { 0 }; - struct membuffer *b = &buf; - - //export translated words here - put_format(b, "translate={"); - - //Dive list view - write_attribute(b, "Number", translate("gettextFromC", "Number"), ", "); - write_attribute(b, "Date", translate("gettextFromC", "Date"), ", "); - write_attribute(b, "Time", translate("gettextFromC", "Time"), ", "); - write_attribute(b, "Location", translate("gettextFromC", "Location"), ", "); - write_attribute(b, "Air_Temp", translate("gettextFromC", "Air temp."), ", "); - write_attribute(b, "Water_Temp", translate("gettextFromC", "Water temp."), ", "); - write_attribute(b, "dives", translate("gettextFromC", "Dives"), ", "); - write_attribute(b, "Expand_All", translate("gettextFromC", "Expand all"), ", "); - write_attribute(b, "Collapse_All", translate("gettextFromC", "Collapse all"), ", "); - write_attribute(b, "trips", translate("gettextFromC", "Trips"), ", "); - write_attribute(b, "Statistics", translate("gettextFromC", "Statistics"), ", "); - write_attribute(b, "Advanced_Search", translate("gettextFromC", "Advanced search"), ", "); - - //Dive expanded view - write_attribute(b, "Rating", translate("gettextFromC", "Rating"), ", "); - write_attribute(b, "Visibility", translate("gettextFromC", "Visibility"), ", "); - write_attribute(b, "Duration", translate("gettextFromC", "Duration"), ", "); - write_attribute(b, "DiveMaster", translate("gettextFromC", "Divemaster"), ", "); - write_attribute(b, "Buddy", translate("gettextFromC", "Buddy"), ", "); - write_attribute(b, "Suit", translate("gettextFromC", "Suit"), ", "); - write_attribute(b, "Tags", translate("gettextFromC", "Tags"), ", "); - write_attribute(b, "Notes", translate("gettextFromC", "Notes"), ", "); - write_attribute(b, "Show_more_details", translate("gettextFromC", "Show more details"), ", "); - - //Yearly statistics view - write_attribute(b, "Yearly_statistics", translate("gettextFromC", "Yearly statistics"), ", "); - write_attribute(b, "Year", translate("gettextFromC", "Year"), ", "); - write_attribute(b, "Total_Time", translate("gettextFromC", "Total time"), ", "); - write_attribute(b, "Average_Time", translate("gettextFromC", "Average time"), ", "); - write_attribute(b, "Shortest_Time", translate("gettextFromC", "Shortest time"), ", "); - write_attribute(b, "Longest_Time", translate("gettextFromC", "Longest time"), ", "); - write_attribute(b, "Average_Depth", translate("gettextFromC", "Average depth"), ", "); - write_attribute(b, "Min_Depth", translate("gettextFromC", "Min. depth"), ", "); - write_attribute(b, "Max_Depth", translate("gettextFromC", "Max. depth"), ", "); - write_attribute(b, "Average_SAC", translate("gettextFromC", "Average SAC"), ", "); - write_attribute(b, "Min_SAC", translate("gettextFromC", "Min. SAC"), ", "); - write_attribute(b, "Max_SAC", translate("gettextFromC", "Max. SAC"), ", "); - write_attribute(b, "Average_Temp", translate("gettextFromC", "Average temp."), ", "); - write_attribute(b, "Min_Temp", translate("gettextFromC", "Min. temp."), ", "); - write_attribute(b, "Max_Temp", translate("gettextFromC", "Max. temp."), ", "); - write_attribute(b, "Back_to_List", translate("gettextFromC", "Back to list"), ", "); - - //dive detailed view - write_attribute(b, "Dive_No", translate("gettextFromC", "Dive No."), ", "); - write_attribute(b, "Dive_profile", translate("gettextFromC", "Dive profile"), ", "); - write_attribute(b, "Dive_information", translate("gettextFromC", "Dive information"), ", "); - write_attribute(b, "Dive_equipment", translate("gettextFromC", "Dive equipment"), ", "); - write_attribute(b, "Type", translate("gettextFromC", "Type"), ", "); - write_attribute(b, "Size", translate("gettextFromC", "Size"), ", "); - write_attribute(b, "Work_Pressure", translate("gettextFromC", "Work pressure"), ", "); - write_attribute(b, "Start_Pressure", translate("gettextFromC", "Start pressure"), ", "); - write_attribute(b, "End_Pressure", translate("gettextFromC", "End pressure"), ", "); - write_attribute(b, "Gas", translate("gettextFromC", "Gas"), ", "); - write_attribute(b, "Weight", translate("gettextFromC", "Weight"), ", "); - write_attribute(b, "Type", translate("gettextFromC", "Type"), ", "); - write_attribute(b, "Events", translate("gettextFromC", "Events"), ", "); - write_attribute(b, "Name", translate("gettextFromC", "Name"), ", "); - write_attribute(b, "Value", translate("gettextFromC", "Value"), ", "); - write_attribute(b, "Coordinates", translate("gettextFromC", "Coordinates"), ", "); - write_attribute(b, "Dive_Status", translate("gettextFromC", "Dive status"), " "); - - put_format(b, "}"); - - f = subsurface_fopen(file_name, "w+"); - if (!f) { - report_error(translate("gettextFromC", "Can't open file %s"), file_name); - } else { - flush_buffer(&buf, f); /*check for writing errors? */ - fclose(f); - } - free_buffer(&buf); -} diff --git a/subsurface-core/save-html.h b/subsurface-core/save-html.h deleted file mode 100644 index 13bb102b1..000000000 --- a/subsurface-core/save-html.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef HTML_SAVE_H -#define HTML_SAVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "dive.h" -#include "membuffer.h" - -void put_HTML_date(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_depth(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_airtemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_watertemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_time(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_notes(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_quoted(struct membuffer *b, const char *text); -void put_HTML_pressure_units(struct membuffer *b, pressure_t pressure, const char *pre, const char *post); -void put_HTML_weight_units(struct membuffer *b, unsigned int grams, const char *pre, const char *post); -void put_HTML_volume_units(struct membuffer *b, unsigned int ml, const char *pre, const char *post); - -void export_HTML(const char *file_name, const char *photos_dir, const bool selected_only, const bool list_only); -void export_list(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only); - -void export_translation(const char *file_name); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/subsurface-core/save-xml.c b/subsurface-core/save-xml.c deleted file mode 100644 index 2335637e8..000000000 --- a/subsurface-core/save-xml.c +++ /dev/null @@ -1,749 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include <stdio.h> -#include <ctype.h> -#include <string.h> -#include <stdlib.h> -#include <errno.h> -#include <time.h> -#include <unistd.h> -#include <fcntl.h> - -#include "dive.h" -#include "divelist.h" -#include "device.h" -#include "membuffer.h" -#include "strndup.h" -#include "git-access.h" -#include "qthelperfromc.h" - -/* - * We're outputting utf8 in xml. - * We need to quote the characters <, >, &. - * - * Technically I don't think we'd necessarily need to quote the control - * characters, but at least libxml2 doesn't like them. It doesn't even - * allow them quoted. So we just skip them and replace them with '?'. - * - * If we do this for attributes, we need to quote the quotes we use too. - */ -static void quote(struct membuffer *b, const char *text, int is_attribute) -{ - int is_html = 0; - put_quoted(b, text, is_attribute, is_html); -} - -static void show_utf8(struct membuffer *b, const char *text, const char *pre, const char *post, int is_attribute) -{ - int len; - char *cleaned; - - if (!text) - return; - /* remove leading and trailing space */ - /* We need to combine isascii() with isspace(), - * because we can only trust isspace() with 7-bit ascii, - * on windows for example */ - while (isascii(*text) && isspace(*text)) - text++; - len = strlen(text); - if (!len) - return; - while (len && isascii(text[len - 1]) && isspace(text[len - 1])) - len--; - cleaned = strndup(text, len); - put_string(b, pre); - quote(b, cleaned, is_attribute); - put_string(b, post); - free(cleaned); -} - -static void save_depths(struct membuffer *b, struct divecomputer *dc) -{ - /* What's the point of this dive entry again? */ - if (!dc->maxdepth.mm && !dc->meandepth.mm) - return; - - put_string(b, " <depth"); - put_depth(b, dc->maxdepth, " max='", " m'"); - put_depth(b, dc->meandepth, " mean='", " m'"); - put_string(b, " />\n"); -} - -static void save_dive_temperature(struct membuffer *b, struct dive *dive) -{ - if (!dive->airtemp.mkelvin && !dive->watertemp.mkelvin) - return; - if (dive->airtemp.mkelvin == dc_airtemp(&dive->dc) && dive->watertemp.mkelvin == dc_watertemp(&dive->dc)) - return; - - put_string(b, " <divetemperature"); - if (dive->airtemp.mkelvin != dc_airtemp(&dive->dc)) - put_temperature(b, dive->airtemp, " air='", " C'"); - if (dive->watertemp.mkelvin != dc_watertemp(&dive->dc)) - put_temperature(b, dive->watertemp, " water='", " C'"); - put_string(b, "/>\n"); -} - -static void save_temperatures(struct membuffer *b, struct divecomputer *dc) -{ - if (!dc->airtemp.mkelvin && !dc->watertemp.mkelvin) - return; - put_string(b, " <temperature"); - put_temperature(b, dc->airtemp, " air='", " C'"); - put_temperature(b, dc->watertemp, " water='", " C'"); - put_string(b, " />\n"); -} - -static void save_airpressure(struct membuffer *b, struct divecomputer *dc) -{ - if (!dc->surface_pressure.mbar) - return; - put_string(b, " <surface"); - put_pressure(b, dc->surface_pressure, " pressure='", " bar'"); - put_string(b, " />\n"); -} - -static void save_salinity(struct membuffer *b, struct divecomputer *dc) -{ - /* only save if we have a value that isn't the default of sea water */ - if (!dc->salinity || dc->salinity == SEAWATER_SALINITY) - return; - put_string(b, " <water"); - put_salinity(b, dc->salinity, " salinity='", " g/l'"); - put_string(b, " />\n"); -} - -static void save_overview(struct membuffer *b, struct dive *dive) -{ - show_utf8(b, dive->divemaster, " <divemaster>", "</divemaster>\n", 0); - show_utf8(b, dive->buddy, " <buddy>", "</buddy>\n", 0); - show_utf8(b, dive->notes, " <notes>", "</notes>\n", 0); - show_utf8(b, dive->suit, " <suit>", "</suit>\n", 0); -} - -static void put_gasmix(struct membuffer *b, struct gasmix *mix) -{ - int o2 = mix->o2.permille; - int he = mix->he.permille; - - if (o2) { - put_format(b, " o2='%u.%u%%'", FRACTION(o2, 10)); - if (he) - put_format(b, " he='%u.%u%%'", FRACTION(he, 10)); - } -} - -static void save_cylinder_info(struct membuffer *b, struct dive *dive) -{ - int i, nr; - - nr = nr_cylinders(dive); - - for (i = 0; i < nr; i++) { - cylinder_t *cylinder = dive->cylinder + i; - int volume = cylinder->type.size.mliter; - const char *description = cylinder->type.description; - - put_format(b, " <cylinder"); - if (volume) - put_milli(b, " size='", volume, " l'"); - put_pressure(b, cylinder->type.workingpressure, " workpressure='", " bar'"); - show_utf8(b, description, " description='", "'", 1); - put_gasmix(b, &cylinder->gasmix); - put_pressure(b, cylinder->start, " start='", " bar'"); - put_pressure(b, cylinder->end, " end='", " bar'"); - if (cylinder->cylinder_use != OC_GAS) - show_utf8(b, cylinderuse_text[cylinder->cylinder_use], " use='", "'", 1); - put_format(b, " />\n"); - } -} - -static void save_weightsystem_info(struct membuffer *b, struct dive *dive) -{ - int i, nr; - - nr = nr_weightsystems(dive); - - for (i = 0; i < nr; i++) { - weightsystem_t *ws = dive->weightsystem + i; - int grams = ws->weight.grams; - const char *description = ws->description; - - put_format(b, " <weightsystem"); - put_milli(b, " weight='", grams, " kg'"); - show_utf8(b, description, " description='", "'", 1); - put_format(b, " />\n"); - } -} - -static void show_index(struct membuffer *b, int value, const char *pre, const char *post) -{ - if (value) - put_format(b, " %s%d%s", pre, value, post); -} - -static void save_sample(struct membuffer *b, struct sample *sample, struct sample *old) -{ - put_format(b, " <sample time='%u:%02u min'", FRACTION(sample->time.seconds, 60)); - put_milli(b, " depth='", sample->depth.mm, " m'"); - if (sample->temperature.mkelvin && sample->temperature.mkelvin != old->temperature.mkelvin) { - put_temperature(b, sample->temperature, " temp='", " C'"); - old->temperature = sample->temperature; - } - put_pressure(b, sample->cylinderpressure, " pressure='", " bar'"); - put_pressure(b, sample->o2cylinderpressure, " o2pressure='", " bar'"); - - /* - * We only show sensor information for samples with pressure, and only if it - * changed from the previous sensor we showed. - */ - if (sample->cylinderpressure.mbar && sample->sensor != old->sensor) { - put_format(b, " sensor='%d'", sample->sensor); - old->sensor = sample->sensor; - } - - /* the deco/ndl values are stored whenever they change */ - if (sample->ndl.seconds != old->ndl.seconds) { - put_format(b, " ndl='%u:%02u min'", FRACTION(sample->ndl.seconds, 60)); - old->ndl = sample->ndl; - } - if (sample->tts.seconds != old->tts.seconds) { - put_format(b, " tts='%u:%02u min'", FRACTION(sample->tts.seconds, 60)); - old->tts = sample->tts; - } - if (sample->rbt.seconds) - put_format(b, " rbt='%u:%02u min'", FRACTION(sample->rbt.seconds, 60)); - if (sample->in_deco != old->in_deco) { - put_format(b, " in_deco='%d'", sample->in_deco ? 1 : 0); - old->in_deco = sample->in_deco; - } - if (sample->stoptime.seconds != old->stoptime.seconds) { - put_format(b, " stoptime='%u:%02u min'", FRACTION(sample->stoptime.seconds, 60)); - old->stoptime = sample->stoptime; - } - - if (sample->stopdepth.mm != old->stopdepth.mm) { - put_milli(b, " stopdepth='", sample->stopdepth.mm, " m'"); - old->stopdepth = sample->stopdepth; - } - - if (sample->cns != old->cns) { - put_format(b, " cns='%u%%'", sample->cns); - old->cns = sample->cns; - } - - if ((sample->o2sensor[0].mbar) && (sample->o2sensor[0].mbar != old->o2sensor[0].mbar)) { - put_milli(b, " sensor1='", sample->o2sensor[0].mbar, " bar'"); - old->o2sensor[0] = sample->o2sensor[0]; - } - - if ((sample->o2sensor[1].mbar) && (sample->o2sensor[1].mbar != old->o2sensor[1].mbar)) { - put_milli(b, " sensor2='", sample->o2sensor[1].mbar, " bar'"); - old->o2sensor[1] = sample->o2sensor[1]; - } - - if ((sample->o2sensor[2].mbar) && (sample->o2sensor[2].mbar != old->o2sensor[2].mbar)) { - put_milli(b, " sensor3='", sample->o2sensor[2].mbar, " bar'"); - old->o2sensor[2] = sample->o2sensor[2]; - } - - if (sample->setpoint.mbar != old->setpoint.mbar) { - put_milli(b, " po2='", sample->setpoint.mbar, " bar'"); - old->setpoint = sample->setpoint; - } - show_index(b, sample->heartbeat, "heartbeat='", "'"); - show_index(b, sample->bearing.degrees, "bearing='", "'"); - put_format(b, " />\n"); -} - -static void save_one_event(struct membuffer *b, struct event *ev) -{ - put_format(b, " <event time='%d:%02d min'", FRACTION(ev->time.seconds, 60)); - show_index(b, ev->type, "type='", "'"); - show_index(b, ev->flags, "flags='", "'"); - show_index(b, ev->value, "value='", "'"); - show_utf8(b, ev->name, " name='", "'", 1); - if (event_is_gaschange(ev)) { - if (ev->gas.index >= 0) { - show_index(b, ev->gas.index, "cylinder='", "'"); - put_gasmix(b, &ev->gas.mix); - } else if (!event_gasmix_redundant(ev)) - put_gasmix(b, &ev->gas.mix); - } - put_format(b, " />\n"); -} - - -static void save_events(struct membuffer *b, struct event *ev) -{ - while (ev) { - save_one_event(b, ev); - ev = ev->next; - } -} - -static void save_tags(struct membuffer *b, struct tag_entry *entry) -{ - if (entry) { - const char *sep = " tags='"; - do { - struct divetag *tag = entry->tag; - put_string(b, sep); - /* If the tag has been translated, write the source to the xml file */ - quote(b, tag->source ?: tag->name, 1); - sep = ", "; - } while ((entry = entry->next) != NULL); - put_string(b, "'"); - } -} - -static void save_extra_data(struct membuffer *b, struct extra_data *ed) -{ - while (ed) { - if (ed->key && ed->value) { - put_string(b, " <extradata"); - show_utf8(b, ed->key, " key='", "'", 1); - show_utf8(b, ed->value, " value='", "'", 1); - put_string(b, " />\n"); - } - ed = ed->next; - } -} - -static void show_date(struct membuffer *b, timestamp_t when) -{ - struct tm tm; - - utc_mkdate(when, &tm); - - put_format(b, " date='%04u-%02u-%02u'", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); - put_format(b, " time='%02u:%02u:%02u'", - tm.tm_hour, tm.tm_min, tm.tm_sec); -} - -static void save_samples(struct membuffer *b, int nr, struct sample *s) -{ - struct sample dummy = {}; - - while (--nr >= 0) { - save_sample(b, s, &dummy); - s++; - } -} - -static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer *dc) -{ - put_format(b, " <divecomputer"); - show_utf8(b, dc->model, " model='", "'", 1); - if (dc->deviceid) - put_format(b, " deviceid='%08x'", dc->deviceid); - if (dc->diveid) - put_format(b, " diveid='%08x'", dc->diveid); - if (dc->when && dc->when != dive->when) - show_date(b, dc->when); - if (dc->duration.seconds && dc->duration.seconds != dive->dc.duration.seconds) - put_duration(b, dc->duration, " duration='", " min'"); - if (dc->divemode != OC) { - for (enum dive_comp_type i = 0; i < NUM_DC_TYPE; i++) - if (dc->divemode == i) - show_utf8(b, divemode_text[i], " dctype='", "'", 1); - if (dc->no_o2sensors) - put_format(b," no_o2sensors='%d'", dc->no_o2sensors); - } - put_format(b, ">\n"); - save_depths(b, dc); - save_temperatures(b, dc); - save_airpressure(b, dc); - save_salinity(b, dc); - put_duration(b, dc->surfacetime, " <surfacetime>", " min</surfacetime>\n"); - save_extra_data(b, dc->extra_data); - save_events(b, dc->events); - save_samples(b, dc->samples, dc->sample); - - put_format(b, " </divecomputer>\n"); -} - -static void save_picture(struct membuffer *b, struct picture *pic) -{ - put_string(b, " <picture filename='"); - put_quoted(b, pic->filename, true, false); - put_string(b, "'"); - if (pic->offset.seconds) { - int offset = pic->offset.seconds; - char sign = '+'; - if (offset < 0) { - sign = '-'; - offset = -offset; - } - put_format(b, " offset='%c%u:%02u min'", sign, FRACTION(offset, 60)); - } - if (pic->latitude.udeg || pic->longitude.udeg) { - 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"); -} - -void save_one_dive_to_mb(struct membuffer *b, struct dive *dive) -{ - struct divecomputer *dc; - - put_string(b, "<dive"); - if (dive->number) - put_format(b, " number='%d'", dive->number); - if (dive->tripflag == NO_TRIP) - put_format(b, " tripflag='NOTRIP'"); - if (dive->rating) - put_format(b, " rating='%d'", dive->rating); - if (dive->visibility) - put_format(b, " visibility='%d'", dive->visibility); - save_tags(b, dive->tag_list); - if (dive->dive_site_uuid) { - if (get_dive_site_by_uuid(dive->dive_site_uuid) != NULL) - put_format(b, " divesiteid='%8x'", dive->dive_site_uuid); - else if (verbose) - fprintf(stderr, "removed reference to non-existant dive site with uuid %08x\n", dive->dive_site_uuid); - } - show_date(b, dive->when); - put_format(b, " duration='%u:%02u min'>\n", - FRACTION(dive->dc.duration.seconds, 60)); - save_overview(b, dive); - save_cylinder_info(b, dive); - save_weightsystem_info(b, dive); - save_dive_temperature(b, dive); - /* Save the dive computer data */ - for_each_dc(dive, dc) - save_dc(b, dive, dc); - FOR_EACH_PICTURE(dive) - save_picture(b, picture); - put_format(b, "</dive>\n"); -} - -int save_dive(FILE *f, struct dive *dive) -{ - struct membuffer buf = { 0 }; - - save_one_dive_to_mb(&buf, dive); - flush_buffer(&buf, f); - /* Error handling? */ - return 0; -} - -static void save_trip(struct membuffer *b, dive_trip_t *trip) -{ - int i; - struct dive *dive; - - put_format(b, "<trip"); - show_date(b, trip->when); - show_utf8(b, trip->location, " location=\'", "\'", 1); - put_format(b, ">\n"); - show_utf8(b, trip->notes, "<notes>", "</notes>\n", 0); - - /* - * Incredibly cheesy: we want to save the dives sorted, and they - * are sorted in the dive array.. So instead of using the dive - * list in the trip, we just traverse the global dive array and - * check the divetrip pointer.. - */ - for_each_dive(i, dive) { - if (dive->divetrip == trip) - save_one_dive_to_mb(b, dive); - } - - put_format(b, "</trip>\n"); -} - -static void save_one_device(void *_f, const char *model, uint32_t deviceid, - const char *nickname, const char *serial_nr, const char *firmware) -{ - struct membuffer *b = _f; - - /* Nicknames that are empty or the same as the device model are not interesting */ - if (nickname) { - if (!*nickname || !strcmp(model, nickname)) - nickname = NULL; - } - - /* Serial numbers that are empty are not interesting */ - if (serial_nr && !*serial_nr) - serial_nr = NULL; - - /* Firmware strings that are empty are not interesting */ - if (firmware && !*firmware) - firmware = NULL; - - /* Do we have anything interesting about this dive computer to save? */ - if (!serial_nr && !nickname && !firmware) - return; - - put_format(b, "<divecomputerid"); - show_utf8(b, model, " model='", "'", 1); - put_format(b, " deviceid='%08x'", deviceid); - show_utf8(b, serial_nr, " serial='", "'", 1); - show_utf8(b, firmware, " firmware='", "'", 1); - show_utf8(b, nickname, " nickname='", "'", 1); - put_format(b, "/>\n"); -} - -int save_dives(const char *filename) -{ - return save_dives_logic(filename, false); -} - -void save_dives_buffer(struct membuffer *b, const bool select_only) -{ - int i; - struct dive *dive; - dive_trip_t *trip; - - put_format(b, "<divelog program='subsurface' version='%d'>\n<settings>\n", DATAFORMAT_VERSION); - - if (prefs.save_userid_local) - put_format(b, " <userid>%30s</userid>\n", prefs.userid); - - /* save the dive computer nicknames, if any */ - call_for_each_dc(b, save_one_device, select_only); - if (autogroup) - put_format(b, " <autogroup state='1' />\n"); - put_format(b, "</settings>\n"); - - /* save the dive sites - to make the output consistent let's sort the table, first */ - dive_site_table_sort(); - put_format(b, "<divesites>\n"); - for (i = 0; i < dive_site_table.nr; i++) { - int j; - struct dive *d; - struct dive_site *ds = get_dive_site(i); - if (dive_site_is_empty(ds)) { - for_each_dive(j, d) { - if (d->dive_site_uuid == ds->uuid) - d->dive_site_uuid = 0; - } - delete_dive_site(ds->uuid); - i--; // since we just deleted that one - continue; - } else if (ds->name && - (strncmp(ds->name, "Auto-created dive", 17) == 0 || - strncmp(ds->name, "New Dive", 8) == 0)) { - // these are the two default names for sites from - // the web service; if the site isn't used in any - // dive (really? you didn't rename it?), delete it - if (!is_dive_site_used(ds->uuid, false)) { - if (verbose) - fprintf(stderr, "Deleted unused auto-created dive site %s\n", ds->name); - delete_dive_site(ds->uuid); - i--; // since we just deleted that one - continue; - } - } - if (select_only && !is_dive_site_used(ds->uuid, true)) - continue; - - put_format(b, "<site uuid='%8x'", ds->uuid); - show_utf8(b, ds->name, " name='", "'", 1); - if (ds->latitude.udeg || ds->longitude.udeg) { - put_degrees(b, ds->latitude, " gps='", " "); - put_degrees(b, ds->longitude, "", "'"); - } - show_utf8(b, ds->description, " description='", "'", 1); - put_format(b, ">\n"); - show_utf8(b, ds->notes, " <notes>", " </notes>\n", 0); - 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->value) { - put_format(b, " <geo cat='%d'", t->category); - put_format(b, " origin='%d'", t->origin); - show_utf8(b, t->value, " value='", "'/>\n", 1); - } - } - } - put_format(b, "</site>\n"); - } - put_format(b, "</divesites>\n<dives>\n"); - for (trip = dive_trip_list; trip != NULL; trip = trip->next) - trip->index = 0; - - /* save the dives */ - for_each_dive(i, dive) { - if (select_only) { - - if (!dive->selected) - continue; - save_one_dive_to_mb(b, dive); - - } else { - trip = dive->divetrip; - - /* Bare dive without a trip? */ - if (!trip) { - save_one_dive_to_mb(b, dive); - continue; - } - - /* Have we already seen this trip (and thus saved this dive?) */ - if (trip->index) - continue; - - /* We haven't seen this trip before - save it and all dives */ - trip->index = 1; - save_trip(b, trip); - } - } - put_format(b, "</dives>\n</divelog>\n"); -} - -static void save_backup(const char *name, const char *ext, const char *new_ext) -{ - int len = strlen(name); - int a = strlen(ext), b = strlen(new_ext); - char *newname; - - /* len up to and including the final '.' */ - len -= a; - if (len <= 1) - return; - if (name[len - 1] != '.') - return; - /* msvc doesn't have strncasecmp, has _strnicmp instead - crazy */ - if (strncasecmp(name + len, ext, a)) - return; - - newname = malloc(len + b + 1); - if (!newname) - return; - - memcpy(newname, name, len); - memcpy(newname + len, new_ext, b + 1); - - /* - * Ignore errors. Maybe we can't create the backup file, - * maybe no old file existed. Regardless, we'll write the - * new file. - */ - (void) subsurface_rename(name, newname); - free(newname); -} - -static void try_to_backup(const char *filename) -{ - char extension[][5] = { "xml", "ssrf", "" }; - int i = 0; - int flen = strlen(filename); - - /* Maybe we might want to make this configurable? */ - while (extension[i][0] != '\0') { - int elen = strlen(extension[i]); - if (strcasecmp(filename + flen - elen, extension[i]) == 0) { - if (last_xml_version < DATAFORMAT_VERSION) { - int se_len = strlen(extension[i]) + 5; - char *special_ext = malloc(se_len); - snprintf(special_ext, se_len, "%s.v%d", extension[i], last_xml_version); - save_backup(filename, extension[i], special_ext); - free(special_ext); - } else { - save_backup(filename, extension[i], "bak"); - } - break; - } - i++; - } -} - -int save_dives_logic(const char *filename, const bool select_only) -{ - struct membuffer buf = { 0 }; - FILE *f; - void *git; - const char *branch, *remote; - int error; - - git = is_git_repository(filename, &branch, &remote, false); - if (git) - return git_save_dives(git, branch, remote, select_only); - - save_dives_buffer(&buf, select_only); - - if (same_string(filename, "-")) { - f = stdout; - } else { - try_to_backup(filename); - error = -1; - f = subsurface_fopen(filename, "w"); - } - if (f) { - flush_buffer(&buf, f); - error = fclose(f); - } - if (error) - report_error("Save failed (%s)", strerror(errno)); - - free_buffer(&buf); - return error; -} - -int export_dives_xslt(const char *filename, const bool selected, const int units, const char *export_xslt) -{ - FILE *f; - struct membuffer buf = { 0 }; - xmlDoc *doc; - xsltStylesheetPtr xslt = NULL; - xmlDoc *transformed; - int res = 0; - char *params[3]; - int pnr = 0; - char unitstr[3]; - - if (verbose) - fprintf(stderr, "export_dives_xslt with stylesheet %s\n", export_xslt); - - if (!filename) - return report_error("No filename for export"); - - /* Save XML to file and convert it into a memory buffer */ - save_dives_buffer(&buf, selected); - - /* - * Parse the memory buffer into XML document and - * transform it to selected export format, finally dumping - * the XML into a character buffer. - */ - doc = xmlReadMemory(buf.buffer, buf.len, "divelog", NULL, 0); - free_buffer(&buf); - if (!doc) - return report_error("Failed to read XML memory"); - - /* Convert to export format */ - xslt = get_stylesheet(export_xslt); - if (!xslt) - return report_error("Failed to open export conversion stylesheet"); - - snprintf(unitstr, 3, "%d", units); - params[pnr++] = "units"; - params[pnr++] = unitstr; - params[pnr++] = NULL; - - transformed = xsltApplyStylesheet(xslt, doc, (const char **)params); - xmlFreeDoc(doc); - - /* Write the transformed export to file */ - f = subsurface_fopen(filename, "w"); - if (f) { - xsltSaveResultToFile(f, transformed, xslt); - fclose(f); - /* Check write errors? */ - } else { - res = report_error("Failed to open %s for writing (%s)", filename, strerror(errno)); - } - xsltFreeStylesheet(xslt); - xmlFreeDoc(transformed); - - return res; -} diff --git a/subsurface-core/serial_ftdi.c b/subsurface-core/serial_ftdi.c deleted file mode 100644 index ff1335171..000000000 --- a/subsurface-core/serial_ftdi.c +++ /dev/null @@ -1,665 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2008 Jef Driesen - * Copyright (C) 2014 Venkatesh Shukla - * Copyright (C) 2015 Anton Lundin - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include <stdlib.h> // malloc, free -#include <string.h> // strerror -#include <errno.h> // errno -#include <sys/time.h> // gettimeofday -#include <time.h> // nanosleep -#include <stdio.h> - -#include <libusb.h> -#include <ftdi.h> - -#ifndef __ANDROID__ -#define INFO(context, fmt, ...) fprintf(stderr, "INFO: " fmt "\n", ##__VA_ARGS__) -#define ERROR(context, fmt, ...) fprintf(stderr, "ERROR: " fmt "\n", ##__VA_ARGS__) -#else -#include <android/log.h> -#define INFO(context, fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, __FILE__, "INFO: " fmt "\n", ##__VA_ARGS__) -#define ERROR(context, fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, __FILE__, "ERROR: " fmt "\n", ##__VA_ARGS__) -#endif -//#define SYSERROR(context, errcode) ERROR(__FILE__ ":" __LINE__ ": %s", strerror(errcode)) -#define SYSERROR(context, errcode) ; - -#include <libdivecomputer/custom_serial.h> - -/* Verbatim copied libdivecomputer enums to support configure */ -typedef enum serial_parity_t { - SERIAL_PARITY_NONE, - SERIAL_PARITY_EVEN, - SERIAL_PARITY_ODD -} serial_parity_t; - -typedef enum serial_flowcontrol_t { - SERIAL_FLOWCONTROL_NONE, - SERIAL_FLOWCONTROL_HARDWARE, - SERIAL_FLOWCONTROL_SOFTWARE -} serial_flowcontrol_t; - -typedef enum serial_queue_t { - SERIAL_QUEUE_INPUT = 0x01, - SERIAL_QUEUE_OUTPUT = 0x02, - SERIAL_QUEUE_BOTH = SERIAL_QUEUE_INPUT | SERIAL_QUEUE_OUTPUT -} serial_queue_t; - -typedef enum serial_line_t { - SERIAL_LINE_DCD, // Data carrier detect - SERIAL_LINE_CTS, // Clear to send - SERIAL_LINE_DSR, // Data set ready - SERIAL_LINE_RNG, // Ring indicator -} serial_line_t; - -#define VID 0x0403 // Vendor ID of FTDI - -#define MAX_BACKOFF 500 // Max milliseconds to wait before timing out. - -typedef struct serial_t { - /* Library context. */ - dc_context_t *context; - /* - * The file descriptor corresponding to the serial port. - * Also a libftdi_ftdi_ctx could be used? - */ - struct ftdi_context *ftdi_ctx; - long timeout; - /* - * Serial port settings are saved into this variable immediately - * after the port is opened. These settings are restored when the - * serial port is closed. - * Saving this using libftdi context or libusb. Search further. - * Custom implementation using libftdi functions could be done. - */ - - /* Half-duplex settings */ - int halfduplex; - unsigned int baudrate; - unsigned int nbits; -} serial_t; - -static int serial_ftdi_get_received (serial_t *device) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - // Direct access is not encouraged. But function implementation - // is not available. The return quantity might be anything. - // Find out further about its possible values and correct way of - // access. - int bytes = device->ftdi_ctx->readbuffer_remaining; - - return bytes; -} - -static int serial_ftdi_get_transmitted (serial_t *device) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - // This is not possible using libftdi. Look further into it. - return -1; -} - -static int serial_ftdi_sleep (serial_t *device, unsigned long timeout) -{ - if (device == NULL) - return -1; - - INFO (device->context, "Sleep: value=%lu", timeout); - - struct timespec ts; - ts.tv_sec = (timeout / 1000); - ts.tv_nsec = (timeout % 1000) * 1000000; - - while (nanosleep (&ts, &ts) != 0) { - if (errno != EINTR ) { - SYSERROR (device->context, errno); - return -1; - } - } - - return 0; -} - - -// Used internally for opening ftdi devices -static int serial_ftdi_open_device (struct ftdi_context *ftdi_ctx) -{ - int accepted_pids[] = { 0x6001, 0x6010, 0x6011, // Suunto (Smart Interface), Heinrichs Weikamp - 0xF460, // Oceanic - 0xF680, // Suunto - 0x87D0, // Cressi (Leonardo) - }; - int num_accepted_pids = 6; - int i, pid, ret; - for (i = 0; i < num_accepted_pids; i++) { - pid = accepted_pids[i]; - ret = ftdi_usb_open (ftdi_ctx, VID, pid); - if (ret == -3) // Device not found - continue; - else - return ret; - } - // No supported devices are attached. - return ret; -} - -// -// Open the serial port. -// Initialise ftdi_context and use it to open the device -// -//FIXME: ugly forward declaration of serial_ftdi_configure, util we support configure for real... -static dc_status_t serial_ftdi_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol); -static dc_status_t serial_ftdi_open (serial_t **out, dc_context_t *context, const char* name) -{ - if (out == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (context, "Open: name=%s", name ? name : ""); - - // Allocate memory. - serial_t *device = (serial_t *) malloc (sizeof (serial_t)); - if (device == NULL) { - SYSERROR (context, errno); - return DC_STATUS_NOMEMORY; - } - - struct ftdi_context *ftdi_ctx = ftdi_new(); - if (ftdi_ctx == NULL) { - free(device); - SYSERROR (context, errno); - return DC_STATUS_NOMEMORY; - } - - // Library context. - device->context = context; - - // Default to blocking reads. - device->timeout = -1; - - // Default to full-duplex. - device->halfduplex = 0; - device->baudrate = 0; - device->nbits = 0; - - // Initialize device ftdi context - ftdi_init(ftdi_ctx); - - if (ftdi_set_interface(ftdi_ctx,INTERFACE_ANY)) { - free(device); - ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); - return DC_STATUS_IO; - } - - if (serial_ftdi_open_device(ftdi_ctx) < 0) { - free(device); - ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); - return DC_STATUS_IO; - } - - if (ftdi_usb_reset(ftdi_ctx)) { - free(device); - ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); - return DC_STATUS_IO; - } - - if (ftdi_usb_purge_buffers(ftdi_ctx)) { - free(device); - ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); - return DC_STATUS_IO; - } - - device->ftdi_ctx = ftdi_ctx; - - //FIXME: remove this when custom-serial have support for configure calls - serial_ftdi_configure (device, 115200, 8, 0, 1, 0); - - *out = device; - - return DC_STATUS_SUCCESS; -} - -// -// Close the serial port. -// -static int serial_ftdi_close (serial_t *device) -{ - if (device == NULL) - return 0; - - // Restore the initial terminal attributes. - // See if it is possible using libusb or libftdi - - int ret = ftdi_usb_close(device->ftdi_ctx); - if (ret < 0) { - ERROR (device->context, "Unable to close the ftdi device : %d (%s)\n", - ret, ftdi_get_error_string(device->ftdi_ctx)); - return ret; - } - - ftdi_free(device->ftdi_ctx); - - // Free memory. - free (device); - - return 0; -} - -// -// Configure the serial port (baudrate, databits, parity, stopbits and flowcontrol). -// -static dc_status_t serial_ftdi_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "Configure: baudrate=%i, databits=%i, parity=%i, stopbits=%i, flowcontrol=%i", - baudrate, databits, parity, stopbits, flowcontrol); - - enum ftdi_bits_type ft_bits; - enum ftdi_stopbits_type ft_stopbits; - enum ftdi_parity_type ft_parity; - - if (ftdi_set_baudrate(device->ftdi_ctx, baudrate) < 0) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - - // Set the character size. - switch (databits) { - case 7: - ft_bits = BITS_7; - break; - case 8: - ft_bits = BITS_8; - break; - default: - return DC_STATUS_INVALIDARGS; - } - - // Set the parity type. - switch (parity) { - case SERIAL_PARITY_NONE: // No parity - ft_parity = NONE; - break; - case SERIAL_PARITY_EVEN: // Even parity - ft_parity = EVEN; - break; - case SERIAL_PARITY_ODD: // Odd parity - ft_parity = ODD; - break; - default: - return DC_STATUS_INVALIDARGS; - } - - // Set the number of stop bits. - switch (stopbits) { - case 1: // One stopbit - ft_stopbits = STOP_BIT_1; - break; - case 2: // Two stopbits - ft_stopbits = STOP_BIT_2; - break; - default: - return DC_STATUS_INVALIDARGS; - } - - // Set the attributes - if (ftdi_set_line_property(device->ftdi_ctx, ft_bits, ft_stopbits, ft_parity)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return DC_STATUS_IO; - } - - // Set the flow control. - switch (flowcontrol) { - case SERIAL_FLOWCONTROL_NONE: // No flow control. - if (ftdi_setflowctrl(device->ftdi_ctx, SIO_DISABLE_FLOW_CTRL) < 0) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return DC_STATUS_IO; - } - break; - case SERIAL_FLOWCONTROL_HARDWARE: // Hardware (RTS/CTS) flow control. - if (ftdi_setflowctrl(device->ftdi_ctx, SIO_RTS_CTS_HS) < 0) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return DC_STATUS_IO; - } - break; - case SERIAL_FLOWCONTROL_SOFTWARE: // Software (XON/XOFF) flow control. - if (ftdi_setflowctrl(device->ftdi_ctx, SIO_XON_XOFF_HS) < 0) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return DC_STATUS_IO; - } - break; - default: - return DC_STATUS_INVALIDARGS; - } - - device->baudrate = baudrate; - device->nbits = 1 + databits + stopbits + (parity ? 1 : 0); - - return DC_STATUS_SUCCESS; -} - -// -// Configure the serial port (timeouts). -// -static int serial_ftdi_set_timeout (serial_t *device, long timeout) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "Timeout: value=%li", timeout); - - device->timeout = timeout; - - return 0; -} - -static int serial_ftdi_set_halfduplex (serial_t *device, int value) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - // Most ftdi chips support full duplex operation. ft232rl does. - // Crosscheck other chips. - - device->halfduplex = value; - - return 0; -} - -static int serial_ftdi_read (serial_t *device, void *data, unsigned int size) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - // The total timeout. - long timeout = device->timeout; - - // The absolute target time. - struct timeval tve; - - static int backoff = 1; - int init = 1; - unsigned int nbytes = 0; - while (nbytes < size) { - struct timeval tvt; - if (timeout > 0) { - struct timeval now; - if (gettimeofday (&now, NULL) != 0) { - SYSERROR (device->context, errno); - return -1; - } - - if (init) { - // Calculate the initial timeout. - tvt.tv_sec = (timeout / 1000); - tvt.tv_usec = (timeout % 1000) * 1000; - // Calculate the target time. - timeradd (&now, &tvt, &tve); - } else { - // Calculate the remaining timeout. - if (timercmp (&now, &tve, <)) - timersub (&tve, &now, &tvt); - else - timerclear (&tvt); - } - init = 0; - } else if (timeout == 0) { - timerclear (&tvt); - } - - int n = ftdi_read_data (device->ftdi_ctx, (char *) data + nbytes, size - nbytes); - if (n < 0) { - if (n == LIBUSB_ERROR_INTERRUPTED) - continue; //Retry. - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; //Error during read call. - } else if (n == 0) { - // Exponential backoff. - if (backoff > MAX_BACKOFF) { - ERROR(device->context, "%s", "FTDI read timed out."); - return -1; - } - serial_ftdi_sleep (device, backoff); - backoff *= 2; - } else { - // Reset backoff to 1 on success. - backoff = 1; - } - - nbytes += n; - } - - INFO (device->context, "Read %d bytes", nbytes); - - return nbytes; -} - -static int serial_ftdi_write (serial_t *device, const void *data, unsigned int size) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - struct timeval tve, tvb; - if (device->halfduplex) { - // Get the current time. - if (gettimeofday (&tvb, NULL) != 0) { - SYSERROR (device->context, errno); - return -1; - } - } - - unsigned int nbytes = 0; - while (nbytes < size) { - - int n = ftdi_write_data (device->ftdi_ctx, (char *) data + nbytes, size - nbytes); - if (n < 0) { - if (n == LIBUSB_ERROR_INTERRUPTED) - continue; // Retry. - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; // Error during write call. - } else if (n == 0) { - break; // EOF. - } - - nbytes += n; - } - - if (device->halfduplex) { - // Get the current time. - if (gettimeofday (&tve, NULL) != 0) { - SYSERROR (device->context, errno); - return -1; - } - - // Calculate the elapsed time (microseconds). - struct timeval tvt; - timersub (&tve, &tvb, &tvt); - unsigned long elapsed = tvt.tv_sec * 1000000 + tvt.tv_usec; - - // Calculate the expected duration (microseconds). A 2 millisecond fudge - // factor is added because it improves the success rate significantly. - unsigned long expected = 1000000.0 * device->nbits / device->baudrate * size + 0.5 + 2000; - - // Wait for the remaining time. - if (elapsed < expected) { - unsigned long remaining = expected - elapsed; - - // The remaining time is rounded up to the nearest millisecond to - // match the Windows implementation. The higher resolution is - // pointless anyway, since we already added a fudge factor above. - serial_ftdi_sleep (device, (remaining + 999) / 1000); - } - } - - INFO (device->context, "Wrote %d bytes", nbytes); - - return nbytes; -} - -static int serial_ftdi_flush (serial_t *device, int queue) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "Flush: queue=%u, input=%i, output=%i", queue, - serial_ftdi_get_received (device), - serial_ftdi_get_transmitted (device)); - - switch (queue) { - case SERIAL_QUEUE_INPUT: - if (ftdi_usb_purge_tx_buffer(device->ftdi_ctx)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - break; - case SERIAL_QUEUE_OUTPUT: - if (ftdi_usb_purge_rx_buffer(device->ftdi_ctx)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - break; - default: - if (ftdi_usb_purge_buffers(device->ftdi_ctx)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - break; - } - - return 0; -} - -static int serial_ftdi_send_break (serial_t *device) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument)a - - INFO (device->context, "Break : One time period."); - - // no direct functions for sending break signals in libftdi. - // there is a suggestion to lower the baudrate and sending NUL - // and resetting the baudrate up again. But it has flaws. - // Not implementing it before researching more. - - return -1; -} - -static int serial_ftdi_set_break (serial_t *device, int level) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "Break: value=%i", level); - - // Not implemented in libftdi yet. Research it further. - - return -1; -} - -static int serial_ftdi_set_dtr (serial_t *device, int level) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "DTR: value=%i", level); - - if (ftdi_setdtr(device->ftdi_ctx, level)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - - return 0; -} - -static int serial_ftdi_set_rts (serial_t *device, int level) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "RTS: value=%i", level); - - if (ftdi_setrts(device->ftdi_ctx, level)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - - return 0; -} - -const dc_serial_operations_t serial_ftdi_ops = { - .open = serial_ftdi_open, - .close = serial_ftdi_close, - .read = serial_ftdi_read, - .write = serial_ftdi_write, - .flush = serial_ftdi_flush, - .get_received = serial_ftdi_get_received, - .get_transmitted = NULL, /*NOT USED ANYWHERE! serial_ftdi_get_transmitted */ - .set_timeout = serial_ftdi_set_timeout -#ifdef FIXED_SSRF_CUSTOM_SERIAL - , - .configure = serial_ftdi_configure, -//static int serial_ftdi_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol) - .set_halfduplex = serial_ftdi_set_halfduplex, -//static int serial_ftdi_set_halfduplex (serial_t *device, int value) - .send_break = serial_ftdi_send_break, -//static int serial_ftdi_send_break (serial_t *device) - .set_break = serial_ftdi_set_break, -//static int serial_ftdi_set_break (serial_t *device, int level) - .set_dtr = serial_ftdi_set_dtr, -//static int serial_ftdi_set_dtr (serial_t *device, int level) - .set_rts = serial_ftdi_set_rts -//static int serial_ftdi_set_rts (serial_t *device, int level) -#endif -}; - -dc_status_t dc_serial_ftdi_open(dc_serial_t **out, dc_context_t *context) -{ - if (out == NULL) - return DC_STATUS_INVALIDARGS; - - // Allocate memory. - dc_serial_t *serial_device = (dc_serial_t *) malloc (sizeof (dc_serial_t)); - - if (serial_device == NULL) { - return DC_STATUS_NOMEMORY; - } - - // Initialize data and function pointers - dc_serial_init(serial_device, NULL, &serial_ftdi_ops); - - // Open the serial device. - dc_status_t rc = (dc_status_t) serial_ftdi_open (&serial_device->port, context, NULL); - if (rc != DC_STATUS_SUCCESS) { - free (serial_device); - return rc; - } - - // Set the type of the device - serial_device->type = DC_TRANSPORT_USB;; - - *out = serial_device; - - return DC_STATUS_SUCCESS; -} diff --git a/subsurface-core/sha1.c b/subsurface-core/sha1.c deleted file mode 100644 index acf8c5d9f..000000000 --- a/subsurface-core/sha1.c +++ /dev/null @@ -1,300 +0,0 @@ -/* - * SHA1 routine optimized to do word accesses rather than byte accesses, - * and to avoid unnecessary copies into the context array. - * - * This was initially based on the Mozilla SHA1 implementation, although - * none of the original Mozilla code remains. - */ - -/* this is only to get definitions for memcpy(), ntohl() and htonl() */ -#include <string.h> -#include <stdint.h> -#ifdef WIN32 -#include <winsock2.h> -#else -#include <arpa/inet.h> -#endif -#include "sha1.h" - -#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) - -/* - * Force usage of rol or ror by selecting the one with the smaller constant. - * It _can_ generate slightly smaller code (a constant of 1 is special), but - * perhaps more importantly it's possibly faster on any uarch that does a - * rotate with a loop. - */ - -#define SHA_ASM(op, x, n) ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; }) -#define SHA_ROL(x, n) SHA_ASM("rol", x, n) -#define SHA_ROR(x, n) SHA_ASM("ror", x, n) - -#else - -#define SHA_ROT(X, l, r) (((X) << (l)) | ((X) >> (r))) -#define SHA_ROL(X, n) SHA_ROT(X, n, 32 - (n)) -#define SHA_ROR(X, n) SHA_ROT(X, 32 - (n), n) - -#endif - -/* - * If you have 32 registers or more, the compiler can (and should) - * try to change the array[] accesses into registers. However, on - * machines with less than ~25 registers, that won't really work, - * and at least gcc will make an unholy mess of it. - * - * So to avoid that mess which just slows things down, we force - * the stores to memory to actually happen (we might be better off - * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as - * suggested by Artur Skawina - that will also make gcc unable to - * try to do the silly "optimize away loads" part because it won't - * see what the value will be). - * - * Ben Herrenschmidt reports that on PPC, the C version comes close - * to the optimized asm with this (ie on PPC you don't want that - * 'volatile', since there are lots of registers). - * - * On ARM we get the best code generation by forcing a full memory barrier - * between each SHA_ROUND, otherwise gcc happily get wild with spilling and - * the stack frame size simply explode and performance goes down the drain. - */ - -#if defined(__i386__) || defined(__x86_64__) -#define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) -#elif defined(__GNUC__) && defined(__arm__) -#define setW(x, val) \ - do { \ - W(x) = (val); \ - __asm__("" :: : "memory"); \ - } while (0) -#else -#define setW(x, val) (W(x) = (val)) -#endif - -/* - * Performance might be improved if the CPU architecture is OK with - * unaligned 32-bit loads and a fast ntohl() is available. - * Otherwise fall back to byte loads and shifts which is portable, - * and is faster on architectures with memory alignment issues. - */ - -#if defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64) || \ - defined(__ppc__) || defined(__ppc64__) || \ - defined(__powerpc__) || defined(__powerpc64__) || \ - defined(__s390__) || defined(__s390x__) - -#define get_be32(p) ntohl(*(unsigned int *)(p)) -#define put_be32(p, v) \ - do { \ - *(unsigned int *)(p) = htonl(v); \ - } while (0) - -#else - -#define get_be32(p) ( \ - (*((unsigned char *)(p) + 0) << 24) | \ - (*((unsigned char *)(p) + 1) << 16) | \ - (*((unsigned char *)(p) + 2) << 8) | \ - (*((unsigned char *)(p) + 3) << 0)) -#define put_be32(p, v) \ - do { \ - unsigned int __v = (v); \ - *((unsigned char *)(p) + 0) = __v >> 24; \ - *((unsigned char *)(p) + 1) = __v >> 16; \ - *((unsigned char *)(p) + 2) = __v >> 8; \ - *((unsigned char *)(p) + 3) = __v >> 0; \ - } while (0) - -#endif - -/* This "rolls" over the 512-bit array */ -#define W(x) (array[(x) & 15]) - -/* - * Where do we get the source from? The first 16 iterations get it from - * the input data, the next mix it from the 512-bit array. - */ -#define SHA_SRC(t) get_be32((unsigned char *)block + (t) * 4) -#define SHA_MIX(t) SHA_ROL(W((t) + 13) ^ W((t) + 8) ^ W((t) + 2) ^ W(t), 1); - -#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) \ - do { \ - unsigned int TEMP = input(t); \ - setW(t, TEMP); \ - E += TEMP + SHA_ROL(A, 5) + (fn) + (constant); \ - B = SHA_ROR(B, 2); \ - } while (0) - -#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C ^ D) & B) ^ D), 0x5a827999, A, B, C, D, E) -#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C ^ D) & B) ^ D), 0x5a827999, A, B, C, D, E) -#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B ^ C ^ D), 0x6ed9eba1, A, B, C, D, E) -#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B &C) + (D &(B ^ C))), 0x8f1bbcdc, A, B, C, D, E) -#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B ^ C ^ D), 0xca62c1d6, A, B, C, D, E) - -static void blk_SHA1_Block(blk_SHA_CTX *ctx, const void *block) -{ - unsigned int A, B, C, D, E; - unsigned int array[16]; - - A = ctx->H[0]; - B = ctx->H[1]; - C = ctx->H[2]; - D = ctx->H[3]; - E = ctx->H[4]; - - /* Round 1 - iterations 0-16 take their input from 'block' */ - T_0_15(0, A, B, C, D, E); - T_0_15(1, E, A, B, C, D); - T_0_15(2, D, E, A, B, C); - T_0_15(3, C, D, E, A, B); - T_0_15(4, B, C, D, E, A); - T_0_15(5, A, B, C, D, E); - T_0_15(6, E, A, B, C, D); - T_0_15(7, D, E, A, B, C); - T_0_15(8, C, D, E, A, B); - T_0_15(9, B, C, D, E, A); - T_0_15(10, A, B, C, D, E); - T_0_15(11, E, A, B, C, D); - T_0_15(12, D, E, A, B, C); - T_0_15(13, C, D, E, A, B); - T_0_15(14, B, C, D, E, A); - T_0_15(15, A, B, C, D, E); - - /* Round 1 - tail. Input from 512-bit mixing array */ - T_16_19(16, E, A, B, C, D); - T_16_19(17, D, E, A, B, C); - T_16_19(18, C, D, E, A, B); - T_16_19(19, B, C, D, E, A); - - /* Round 2 */ - T_20_39(20, A, B, C, D, E); - T_20_39(21, E, A, B, C, D); - T_20_39(22, D, E, A, B, C); - T_20_39(23, C, D, E, A, B); - T_20_39(24, B, C, D, E, A); - T_20_39(25, A, B, C, D, E); - T_20_39(26, E, A, B, C, D); - T_20_39(27, D, E, A, B, C); - T_20_39(28, C, D, E, A, B); - T_20_39(29, B, C, D, E, A); - T_20_39(30, A, B, C, D, E); - T_20_39(31, E, A, B, C, D); - T_20_39(32, D, E, A, B, C); - T_20_39(33, C, D, E, A, B); - T_20_39(34, B, C, D, E, A); - T_20_39(35, A, B, C, D, E); - T_20_39(36, E, A, B, C, D); - T_20_39(37, D, E, A, B, C); - T_20_39(38, C, D, E, A, B); - T_20_39(39, B, C, D, E, A); - - /* Round 3 */ - T_40_59(40, A, B, C, D, E); - T_40_59(41, E, A, B, C, D); - T_40_59(42, D, E, A, B, C); - T_40_59(43, C, D, E, A, B); - T_40_59(44, B, C, D, E, A); - T_40_59(45, A, B, C, D, E); - T_40_59(46, E, A, B, C, D); - T_40_59(47, D, E, A, B, C); - T_40_59(48, C, D, E, A, B); - T_40_59(49, B, C, D, E, A); - T_40_59(50, A, B, C, D, E); - T_40_59(51, E, A, B, C, D); - T_40_59(52, D, E, A, B, C); - T_40_59(53, C, D, E, A, B); - T_40_59(54, B, C, D, E, A); - T_40_59(55, A, B, C, D, E); - T_40_59(56, E, A, B, C, D); - T_40_59(57, D, E, A, B, C); - T_40_59(58, C, D, E, A, B); - T_40_59(59, B, C, D, E, A); - - /* Round 4 */ - T_60_79(60, A, B, C, D, E); - T_60_79(61, E, A, B, C, D); - T_60_79(62, D, E, A, B, C); - T_60_79(63, C, D, E, A, B); - T_60_79(64, B, C, D, E, A); - T_60_79(65, A, B, C, D, E); - T_60_79(66, E, A, B, C, D); - T_60_79(67, D, E, A, B, C); - T_60_79(68, C, D, E, A, B); - T_60_79(69, B, C, D, E, A); - T_60_79(70, A, B, C, D, E); - T_60_79(71, E, A, B, C, D); - T_60_79(72, D, E, A, B, C); - T_60_79(73, C, D, E, A, B); - T_60_79(74, B, C, D, E, A); - T_60_79(75, A, B, C, D, E); - T_60_79(76, E, A, B, C, D); - T_60_79(77, D, E, A, B, C); - T_60_79(78, C, D, E, A, B); - T_60_79(79, B, C, D, E, A); - - ctx->H[0] += A; - ctx->H[1] += B; - ctx->H[2] += C; - ctx->H[3] += D; - ctx->H[4] += E; -} - -void blk_SHA1_Init(blk_SHA_CTX *ctx) -{ - ctx->size = 0; - - /* Initialize H with the magic constants (see FIPS180 for constants) */ - ctx->H[0] = 0x67452301; - ctx->H[1] = 0xefcdab89; - ctx->H[2] = 0x98badcfe; - ctx->H[3] = 0x10325476; - ctx->H[4] = 0xc3d2e1f0; -} - -void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len) -{ - unsigned int lenW = ctx->size & 63; - - ctx->size += len; - - /* Read the data into W and process blocks as they get full */ - if (lenW) { - unsigned int left = 64 - lenW; - if (len < left) - left = len; - memcpy(lenW + (char *)ctx->W, data, left); - lenW = (lenW + left) & 63; - len -= left; - data = ((const char *)data + left); - if (lenW) - return; - blk_SHA1_Block(ctx, ctx->W); - } - while (len >= 64) { - blk_SHA1_Block(ctx, data); - data = ((const char *)data + 64); - len -= 64; - } - if (len) - memcpy(ctx->W, data, len); -} - -void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx) -{ - static const unsigned char pad[64] = { 0x80 }; - unsigned int padlen[2]; - int i; - - /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ - padlen[0] = htonl((uint32_t)(ctx->size >> 29)); - padlen[1] = htonl((uint32_t)(ctx->size << 3)); - - i = ctx->size & 63; - blk_SHA1_Update(ctx, pad, 1 + (63 & (55 - i))); - blk_SHA1_Update(ctx, padlen, 8); - - /* Output hash */ - for (i = 0; i < 5; i++) - put_be32(hashout + i * 4, ctx->H[i]); -} diff --git a/subsurface-core/sha1.h b/subsurface-core/sha1.h deleted file mode 100644 index cab6ff77d..000000000 --- a/subsurface-core/sha1.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SHA1 routine optimized to do word accesses rather than byte accesses, - * and to avoid unnecessary copies into the context array. - * - * This was initially based on the Mozilla SHA1 implementation, although - * none of the original Mozilla code remains. - */ -#ifndef SHA1_H -#define SHA1_H - -typedef struct -{ - unsigned long long size; - unsigned int H[5]; - unsigned int W[16]; -} blk_SHA_CTX; - -void blk_SHA1_Init(blk_SHA_CTX *ctx); -void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len); -void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); - -/* Make us use the standard names */ -#define SHA_CTX blk_SHA_CTX -#define SHA1_Init blk_SHA1_Init -#define SHA1_Update blk_SHA1_Update -#define SHA1_Final blk_SHA1_Final - -/* Trivial helper function */ -static inline void SHA1(const void *dataIn, unsigned long len, unsigned char hashout[20]) -{ - SHA_CTX ctx; - - SHA1_Init(&ctx); - SHA1_Update(&ctx, dataIn, len); - SHA1_Final(hashout, &ctx); -} - -#endif // SHA1_H diff --git a/subsurface-core/statistics.c b/subsurface-core/statistics.c deleted file mode 100644 index 6a05cffc1..000000000 --- a/subsurface-core/statistics.c +++ /dev/null @@ -1,404 +0,0 @@ -/* statistics.c - * - * core logic for the Info & Stats page - - * char *get_time_string(int seconds, int maxdays); - * char *get_minutes(int seconds); - * void process_all_dives(struct dive *dive, struct dive **prev_dive); - * void get_selected_dives_text(char *buffer, int size); - */ -#include "gettext.h" -#include <string.h> -#include <ctype.h> - -#include "dive.h" -#include "display.h" -#include "divelist.h" -#include "statistics.h" - -static stats_t stats; -stats_t stats_selection; -stats_t *stats_monthly = NULL; -stats_t *stats_yearly = NULL; -stats_t *stats_by_trip = NULL; -stats_t *stats_by_type = NULL; - -static void process_temperatures(struct dive *dp, stats_t *stats) -{ - int min_temp, mean_temp, max_temp = 0; - - max_temp = dp->maxtemp.mkelvin; - if (max_temp && (!stats->max_temp || max_temp > stats->max_temp)) - stats->max_temp = max_temp; - - min_temp = dp->mintemp.mkelvin; - if (min_temp && (!stats->min_temp || min_temp < stats->min_temp)) - stats->min_temp = min_temp; - - if (min_temp || max_temp) { - mean_temp = min_temp; - if (mean_temp) - mean_temp = (mean_temp + max_temp) / 2; - else - mean_temp = max_temp; - stats->combined_temp += get_temp_units(mean_temp, NULL); - stats->combined_count++; - } -} - -static void process_dive(struct dive *dp, stats_t *stats) -{ - int old_tt, sac_time = 0; - uint32_t duration = dp->duration.seconds; - - old_tt = stats->total_time.seconds; - stats->total_time.seconds += duration; - if (duration > stats->longest_time.seconds) - stats->longest_time.seconds = duration; - if (stats->shortest_time.seconds == 0 || duration < stats->shortest_time.seconds) - stats->shortest_time.seconds = duration; - if (dp->maxdepth.mm > stats->max_depth.mm) - stats->max_depth.mm = dp->maxdepth.mm; - if (stats->min_depth.mm == 0 || dp->maxdepth.mm < stats->min_depth.mm) - stats->min_depth.mm = dp->maxdepth.mm; - - process_temperatures(dp, stats); - - /* Maybe we should drop zero-duration dives */ - if (!duration) - return; - stats->avg_depth.mm = (1.0 * old_tt * stats->avg_depth.mm + - duration * dp->meandepth.mm) / - stats->total_time.seconds; - if (dp->sac > 100) { /* less than .1 l/min is bogus, even with a pSCR */ - sac_time = stats->total_sac_time + duration; - stats->avg_sac.mliter = (1.0 * stats->total_sac_time * stats->avg_sac.mliter + - duration * dp->sac) / - sac_time; - if (dp->sac > stats->max_sac.mliter) - stats->max_sac.mliter = dp->sac; - if (stats->min_sac.mliter == 0 || dp->sac < stats->min_sac.mliter) - stats->min_sac.mliter = dp->sac; - stats->total_sac_time = sac_time; - } -} - -char *get_minutes(int seconds) -{ - static char buf[80]; - snprintf(buf, sizeof(buf), "%d:%.2d", FRACTION(seconds, 60)); - return buf; -} - -void process_all_dives(struct dive *dive, struct dive **prev_dive) -{ - int idx; - struct dive *dp; - struct tm tm; - int current_year = 0; - int current_month = 0; - int year_iter = 0; - int month_iter = 0; - int prev_month = 0, prev_year = 0; - int trip_iter = 0; - dive_trip_t *trip_ptr = 0; - unsigned int size, tsize; - - *prev_dive = NULL; - memset(&stats, 0, sizeof(stats)); - if (dive_table.nr > 0) { - stats.shortest_time.seconds = dive_table.dives[0]->duration.seconds; - stats.min_depth.mm = dive_table.dives[0]->maxdepth.mm; - stats.selection_size = dive_table.nr; - } - - /* allocate sufficient space to hold the worst - * case (one dive per year or all dives during - * one month) for yearly and monthly statistics*/ - - free(stats_yearly); - free(stats_monthly); - free(stats_by_trip); - free(stats_by_type); - - size = sizeof(stats_t) * (dive_table.nr + 1); - tsize = sizeof(stats_t) * (NUM_DC_TYPE + 1); - stats_yearly = malloc(size); - stats_monthly = malloc(size); - stats_by_trip = malloc(size); - stats_by_type = malloc(tsize); - if (!stats_yearly || !stats_monthly || !stats_by_trip || !stats_by_type) - return; - memset(stats_yearly, 0, size); - memset(stats_monthly, 0, size); - memset(stats_by_trip, 0, size); - memset(stats_by_type, 0, tsize); - stats_yearly[0].is_year = true; - - /* Setting the is_trip to true to show the location as first - * field in the statistics window */ - stats_by_type[0].location = strdup("All (by type stats)"); - stats_by_type[0].is_trip = true; - stats_by_type[1].location = strdup("OC"); - stats_by_type[1].is_trip = true; - stats_by_type[2].location = strdup("CCR"); - stats_by_type[2].is_trip = true; - stats_by_type[3].location = strdup("pSCR"); - stats_by_type[3].is_trip = true; - stats_by_type[4].location = strdup("Freedive"); - stats_by_type[4].is_trip = true; - - /* this relies on the fact that the dives in the dive_table - * are in chronological order */ - for_each_dive (idx, dp) { - if (dive && dp->when == dive->when) { - /* that's the one we are showing */ - if (idx > 0) - *prev_dive = dive_table.dives[idx - 1]; - } - process_dive(dp, &stats); - - /* yearly statistics */ - utc_mkdate(dp->when, &tm); - if (current_year == 0) - current_year = tm.tm_year + 1900; - - if (current_year != tm.tm_year + 1900) { - current_year = tm.tm_year + 1900; - process_dive(dp, &(stats_yearly[++year_iter])); - stats_yearly[year_iter].is_year = true; - } else { - process_dive(dp, &(stats_yearly[year_iter])); - } - stats_yearly[year_iter].selection_size++; - stats_yearly[year_iter].period = current_year; - - /* stats_by_type[0] is all the dives combined */ - stats_by_type[0].selection_size++; - process_dive(dp, &(stats_by_type[0])); - - process_dive(dp, &(stats_by_type[dp->dc.divemode + 1])); - stats_by_type[dp->dc.divemode + 1].selection_size++; - - if (dp->divetrip != NULL) { - if (trip_ptr != dp->divetrip) { - trip_ptr = dp->divetrip; - trip_iter++; - } - - /* stats_by_trip[0] is all the dives combined */ - stats_by_trip[0].selection_size++; - process_dive(dp, &(stats_by_trip[0])); - stats_by_trip[0].is_trip = true; - stats_by_trip[0].location = strdup("All (by trip stats)"); - - process_dive(dp, &(stats_by_trip[trip_iter])); - stats_by_trip[trip_iter].selection_size++; - stats_by_trip[trip_iter].is_trip = true; - stats_by_trip[trip_iter].location = dp->divetrip->location; - } - - /* monthly statistics */ - if (current_month == 0) { - current_month = tm.tm_mon + 1; - } else { - if (current_month != tm.tm_mon + 1) - current_month = tm.tm_mon + 1; - if (prev_month != current_month || prev_year != current_year) - month_iter++; - } - process_dive(dp, &(stats_monthly[month_iter])); - stats_monthly[month_iter].selection_size++; - stats_monthly[month_iter].period = current_month; - prev_month = current_month; - prev_year = current_year; - } -} - -/* make sure we skip the selected summary entries */ -void process_selected_dives(void) -{ - struct dive *dive; - unsigned int i, nr; - - memset(&stats_selection, 0, sizeof(stats_selection)); - - nr = 0; - for_each_dive(i, dive) { - if (dive->selected) { - process_dive(dive, &stats_selection); - nr++; - } - } - stats_selection.selection_size = nr; -} - -char *get_time_string_s(int seconds, int maxdays, bool freediving) -{ - static char buf[80]; - if (maxdays && seconds > 3600 * 24 * maxdays) { - snprintf(buf, sizeof(buf), translate("gettextFromC", "more than %d days"), maxdays); - } else { - int days = seconds / 3600 / 24; - int hours = (seconds - days * 3600 * 24) / 3600; - int minutes = (seconds - days * 3600 * 24 - hours * 3600) / 60; - int secs = (seconds - days * 3600 * 24 - hours * 3600 - minutes*60); - if (days > 0) - snprintf(buf, sizeof(buf), translate("gettextFromC", "%dd %dh %dmin"), days, hours, minutes); - else - if (freediving && seconds < 3600) - snprintf(buf, sizeof(buf), translate("gettextFromC", "%dmin %dsecs"), minutes, secs); - else - snprintf(buf, sizeof(buf), translate("gettextFromC", "%dh %dmin"), hours, minutes); - } - return buf; -} - -/* this gets called when at least two but not all dives are selected */ -static void get_ranges(char *buffer, int size) -{ - int i, len; - int first = -1, last = -1; - struct dive *dive; - - snprintf(buffer, size, "%s", translate("gettextFromC", "for dives #")); - for_each_dive (i, dive) { - if (!dive->selected) - continue; - if (dive->number < 1) { - /* uhh - weird numbers - bail */ - snprintf(buffer, size, "%s", translate("gettextFromC", "for selected dives")); - return; - } - len = strlen(buffer); - if (last == -1) { - snprintf(buffer + len, size - len, "%d", dive->number); - first = last = dive->number; - } else { - if (dive->number == last + 1) { - last++; - continue; - } else { - if (first == last) - snprintf(buffer + len, size - len, ", %d", dive->number); - else if (first + 1 == last) - snprintf(buffer + len, size - len, ", %d, %d", last, dive->number); - else - snprintf(buffer + len, size - len, "-%d, %d", last, dive->number); - first = last = dive->number; - } - } - } - len = strlen(buffer); - if (first != last) { - if (first + 1 == last) - snprintf(buffer + len, size - len, ", %d", last); - else - snprintf(buffer + len, size - len, "-%d", last); - } -} - -void get_selected_dives_text(char *buffer, size_t size) -{ - if (amount_selected == 1) { - if (current_dive) - snprintf(buffer, size, translate("gettextFromC", "for dive #%d"), current_dive->number); - else - snprintf(buffer, size, "%s", translate("gettextFromC", "for selected dive")); - } else if (amount_selected == (unsigned int)dive_table.nr) { - snprintf(buffer, size, "%s", translate("gettextFromC", "for all dives")); - } else if (amount_selected == 0) { - snprintf(buffer, size, "%s", translate("gettextFromC", "(no dives)")); - } else { - get_ranges(buffer, size); - if (strlen(buffer) == size - 1) { - /* add our own ellipse... the way Pango does this is ugly - * as it will leave partial numbers there which I don't like */ - size_t offset = 4; - while (offset < size && isdigit(buffer[size - offset])) - offset++; - strcpy(buffer + size - offset, "..."); - } - } -} - -#define SOME_GAS 5000 // 5bar drop in cylinder pressure makes cylinder used - -bool is_cylinder_used(struct dive *dive, int idx) -{ - struct divecomputer *dc; - bool firstGasExplicit = false; - if (cylinder_none(&dive->cylinder[idx])) - return false; - - if ((dive->cylinder[idx].start.mbar - dive->cylinder[idx].end.mbar) > SOME_GAS) - return true; - for_each_dc(dive, dc) { - struct event *event = get_next_event(dc->events, "gaschange"); - while (event) { - if (dc->sample && (event->time.seconds == 0 || - (dc->samples && dc->sample[0].time.seconds == event->time.seconds))) - firstGasExplicit = true; - if (get_cylinder_index(dive, event) == idx) - return true; - event = get_next_event(event->next, "gaschange"); - } - if (dc->divemode == CCR && (idx == dive->diluent_cylinder_index || idx == dive->oxygen_cylinder_index)) - return true; - } - if (idx == 0 && !firstGasExplicit) - return true; - return false; -} - -void get_gas_used(struct dive *dive, volume_t gases[MAX_CYLINDERS]) -{ - int idx; - for (idx = 0; idx < MAX_CYLINDERS; idx++) { - cylinder_t *cyl = &dive->cylinder[idx]; - pressure_t start, end; - - if (!is_cylinder_used(dive, idx)) - continue; - - start = cyl->start.mbar ? cyl->start : cyl->sample_start; - end = cyl->end.mbar ? cyl->end : cyl->sample_end; - if (end.mbar && start.mbar > end.mbar) - gases[idx].mliter = gas_volume(cyl, start) - gas_volume(cyl, end); - } -} - -/* Quite crude reverse-blender-function, but it produces a approx result */ -static void get_gas_parts(struct gasmix mix, volume_t vol, int o2_in_topup, volume_t *o2, volume_t *he) -{ - volume_t air = {}; - - if (gasmix_is_air(&mix)) { - o2->mliter = 0; - he->mliter = 0; - return; - } - - air.mliter = rint(((double)vol.mliter * (1000 - get_he(&mix) - get_o2(&mix))) / (1000 - o2_in_topup)); - he->mliter = rint(((double)vol.mliter * get_he(&mix)) / 1000.0); - o2->mliter += vol.mliter - he->mliter - air.mliter; -} - -void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot) -{ - int i, j; - struct dive *d; - 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) { - volume_t o2 = {}, he = {}; - get_gas_parts(d->cylinder[j].gasmix, diveGases[j], O2_IN_AIR, &o2, &he); - o2_tot->mliter += o2.mliter; - he_tot->mliter += he.mliter; - } - } - } -} diff --git a/subsurface-core/statistics.h b/subsurface-core/statistics.h deleted file mode 100644 index 015c3481e..000000000 --- a/subsurface-core/statistics.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * statistics.h - * - * core logic functions called from statistics UI - * common types and variables - */ - -#ifndef STATISTICS_H -#define STATISTICS_H - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct -{ - int period; - duration_t total_time; - /* avg_time is simply total_time / nr -- let's not keep this */ - duration_t shortest_time; - duration_t longest_time; - depth_t max_depth; - depth_t min_depth; - depth_t avg_depth; - volume_t max_sac; - volume_t min_sac; - volume_t avg_sac; - int max_temp; - int min_temp; - double combined_temp; - unsigned int combined_count; - unsigned int selection_size; - unsigned int total_sac_time; - bool is_year; - bool is_trip; - char *location; -} stats_t; -extern stats_t stats_selection; -extern stats_t *stats_yearly; -extern stats_t *stats_monthly; -extern stats_t *stats_by_trip; -extern stats_t *stats_by_type; - -extern char *get_time_string_s(int seconds, int maxdays, bool freediving); -extern char *get_minutes(int seconds); -extern void process_all_dives(struct dive *dive, struct dive **prev_dive); -extern void get_selected_dives_text(char *buffer, size_t size); -extern void get_gas_used(struct dive *dive, volume_t gases[MAX_CYLINDERS]); -extern void process_selected_dives(void); -void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot); - -inline char *get_time_string(int seconds, int maxdays) { - return get_time_string_s( seconds, maxdays, false); -} -#ifdef __cplusplus -} -#endif - -#endif // STATISTICS_H diff --git a/subsurface-core/strndup.h b/subsurface-core/strndup.h deleted file mode 100644 index 84e18b60f..000000000 --- a/subsurface-core/strndup.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef STRNDUP_H -#define STRNDUP_H -#if __WIN32__ -static char *strndup (const char *s, size_t n) -{ - char *cpy; - size_t len = strlen(s); - if (n < len) - len = n; - if ((cpy = malloc(len + 1)) != - NULL) { - cpy[len] = - '\0'; - memcpy(cpy, - s, - len); - } - return cpy; -} -#endif -#endif /* STRNDUP_H */ diff --git a/subsurface-core/strtod.c b/subsurface-core/strtod.c deleted file mode 100644 index 81e5d42d1..000000000 --- a/subsurface-core/strtod.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Sane helper for 'strtod()'. - * - * Sad that we even need this, but the C library version has - * insane locale behavior, and while the Qt "doDouble()" routines - * are better in that regard, they don't have an end pointer - * (having replaced it with the completely idiotic "ok" boolean - * pointer instead). - * - * I wonder what drugs people are on sometimes. - * - * Right now we support the following flags to limit the - * parsing some ways: - * - * STRTOD_NO_SIGN - don't accept signs - * STRTOD_NO_DOT - no decimal dots, I'm European - * STRTOD_NO_COMMA - no comma, please, I'm C locale - * STRTOD_NO_EXPONENT - no exponent parsing, I'm human - * - * The "negative" flags are so that the common case can just - * use a flag value of 0, and only if you have some special - * requirements do you need to state those with explicit flags. - * - * So if you want the C locale kind of parsing, you'd use the - * STRTOD_NO_COMMA flag to disallow a decimal comma. But if you - * want a more relaxed "Hey, Europeans are people too, even if - * they have locales with commas", just pass in a zero flag. - */ -#include <ctype.h> -#include "dive.h" - -double strtod_flags(const char *str, const char **ptr, unsigned int flags) -{ - char c; - const char *p = str, *ep; - double val = 0.0; - double decimal = 1.0; - int sign = 0, esign = 0; - int numbers = 0, dot = 0; - - /* skip spaces */ - while (isspace(c = *p++)) - /* */; - - /* optional sign */ - if (!(flags & STRTOD_NO_SIGN)) { - switch (c) { - case '-': - sign = 1; - /* fallthrough */ - case '+': - c = *p++; - } - } - - /* Mantissa */ - for (;; c = *p++) { - if ((c == '.' && !(flags & STRTOD_NO_DOT)) || - (c == ',' && !(flags & STRTOD_NO_COMMA))) { - if (dot) - goto done; - dot = 1; - continue; - } - if (c >= '0' && c <= '9') { - numbers++; - val = (val * 10) + (c - '0'); - if (dot) - decimal *= 10; - continue; - } - if (c != 'e' && c != 'E') - goto done; - if (flags & STRTOD_NO_EXPONENT) - goto done; - break; - } - - if (!numbers) - goto done; - - /* Exponent */ - ep = p; - c = *ep++; - switch (c) { - case '-': - esign = 1; - /* fallthrough */ - case '+': - c = *ep++; - } - - if (c >= '0' && c <= '9') { - p = ep; - int exponent = c - '0'; - - for (;;) { - c = *p++; - if (c < '0' || c > '9') - break; - exponent *= 10; - exponent += c - '0'; - } - - /* We're not going to bother playing games */ - if (exponent > 308) - exponent = 308; - - while (exponent-- > 0) { - if (esign) - decimal *= 10; - else - decimal /= 10; - } - } - -done: - if (!numbers) - goto no_conversion; - if (ptr) - *ptr = p - 1; - return (sign ? -val : val) / decimal; - -no_conversion: - if (ptr) - *ptr = str; - return 0.0; -} diff --git a/subsurface-core/subsurface-qt/DiveObjectHelper.cpp b/subsurface-core/subsurface-qt/DiveObjectHelper.cpp deleted file mode 100644 index ab88d2f3c..000000000 --- a/subsurface-core/subsurface-qt/DiveObjectHelper.cpp +++ /dev/null @@ -1,338 +0,0 @@ -#include "DiveObjectHelper.h" - -#include <QDateTime> -#include <QTextDocument> - -#include "../qthelper.h" -#include "../helpers.h" - -static QString EMPTY_DIVE_STRING = QStringLiteral("--"); -enum returnPressureSelector {START_PRESSURE, END_PRESSURE}; - -static QString getFormattedWeight(struct dive *dive, unsigned int idx) -{ - weightsystem_t *weight = &dive->weightsystem[idx]; - if (!weight->description) - return QString(EMPTY_DIVE_STRING); - QString fmt = QString(weight->description); - fmt += ", " + get_weight_string(weight->weight, true); - return fmt; -} - -static QString getFormattedCylinder(struct dive *dive, unsigned int idx) -{ - cylinder_t *cyl = &dive->cylinder[idx]; - const char *desc = cyl->type.description; - if (!desc && idx > 0) - return QString(EMPTY_DIVE_STRING); - QString fmt = desc ? QString(desc) : QObject::tr("unknown"); - fmt += ", " + get_volume_string(cyl->type.size, true); - fmt += ", " + get_pressure_string(cyl->type.workingpressure, true); - fmt += ", " + get_pressure_string(cyl->start, false) + " - " + get_pressure_string(cyl->end, true); - fmt += ", " + get_gas_string(cyl->gasmix); - return fmt; -} - -static QString getPressures(struct dive *dive, enum returnPressureSelector ret) -{ - cylinder_t *cyl = &dive->cylinder[0]; - QString fmt; - if (ret == START_PRESSURE) { - if (cyl->start.mbar) - fmt = get_pressure_string(cyl->start, true); - else if (cyl->sample_start.mbar) - fmt = get_pressure_string(cyl->sample_start, true); - } - if (ret == END_PRESSURE) { - if (cyl->end.mbar) - fmt = get_pressure_string(cyl->end, true); - else if(cyl->sample_end.mbar) - fmt = get_pressure_string(cyl->sample_end, true); - } - return fmt; -} - -DiveObjectHelper::DiveObjectHelper(struct dive *d) : - m_dive(d) -{ -} - -DiveObjectHelper::~DiveObjectHelper() -{ -} - -int DiveObjectHelper::number() const -{ - return m_dive->number; -} - -int DiveObjectHelper::id() const -{ - return m_dive->id; -} - -QString DiveObjectHelper::date() const -{ - QDateTime localTime = QDateTime::fromTime_t(m_dive->when - gettimezoneoffset(m_dive->when)); - localTime.setTimeSpec(Qt::UTC); - return localTime.date().toString(prefs.date_format); -} - -timestamp_t DiveObjectHelper::timestamp() const -{ - return m_dive->when; -} - -QString DiveObjectHelper::time() const -{ - QDateTime localTime = QDateTime::fromTime_t(m_dive->when - gettimezoneoffset(m_dive->when)); - localTime.setTimeSpec(Qt::UTC); - return localTime.time().toString(prefs.time_format); -} - -QString DiveObjectHelper::location() const -{ - return get_dive_location(m_dive) ? QString::fromUtf8(get_dive_location(m_dive)) : EMPTY_DIVE_STRING; -} - -QString DiveObjectHelper::gps() const -{ - struct dive_site *ds = get_dive_site_by_uuid(m_dive->dive_site_uuid); - return ds ? QString(printGPSCoords(ds->latitude.udeg, ds->longitude.udeg)) : QString(); -} -QString DiveObjectHelper::duration() const -{ - return get_dive_duration_string(m_dive->duration.seconds, QObject::tr("h:"), QObject::tr("min")); -} - -bool DiveObjectHelper::noDive() const -{ - return m_dive->duration.seconds == 0 && m_dive->dc.duration.seconds == 0; -} - -QString DiveObjectHelper::depth() const -{ - return get_depth_string(m_dive->dc.maxdepth.mm, true, true); -} - -QString DiveObjectHelper::divemaster() const -{ - return m_dive->divemaster ? m_dive->divemaster : EMPTY_DIVE_STRING; -} - -QString DiveObjectHelper::buddy() const -{ - return m_dive->buddy ? m_dive->buddy : EMPTY_DIVE_STRING; -} - -QString DiveObjectHelper::airTemp() const -{ - QString temp = get_temperature_string(m_dive->airtemp, true); - if (temp.isEmpty()) { - temp = EMPTY_DIVE_STRING; - } - return temp; -} - -QString DiveObjectHelper::waterTemp() const -{ - QString temp = get_temperature_string(m_dive->watertemp, true); - if (temp.isEmpty()) { - temp = EMPTY_DIVE_STRING; - } - return temp; -} - -QString DiveObjectHelper::notes() const -{ - QString tmp = m_dive->notes ? QString::fromUtf8(m_dive->notes) : EMPTY_DIVE_STRING; - if (same_string(m_dive->dc.model, "planned dive")) { - QTextDocument notes; - #define _NOTES_BR "\n" - tmp.replace("<thead>", "<thead>" _NOTES_BR) - .replace("<br>", "<br>" _NOTES_BR) - .replace("<tr>", "<tr>" _NOTES_BR) - .replace("</tr>", "</tr>" _NOTES_BR); - notes.setHtml(tmp); - tmp = notes.toPlainText(); - tmp.replace(_NOTES_BR, "<br>"); - #undef _NOTES_BR - } else { - tmp.replace("\n", "<br>"); - } - return tmp; -} - -QString DiveObjectHelper::tags() const -{ - static char buffer[256]; - taglist_get_tagstring(m_dive->tag_list, buffer, 256); - return QString(buffer); -} - -QString DiveObjectHelper::gas() const -{ - /*WARNING: here should be the gastlist, returned - * from the get_gas_string function or this is correct? - */ - QString gas, gases; - for (int i = 0; i < MAX_CYLINDERS; i++) { - if (!is_cylinder_used(m_dive, i)) - continue; - gas = m_dive->cylinder[i].type.description; - if (!gas.isEmpty()) - gas += QChar(' '); - gas += gasname(&m_dive->cylinder[i].gasmix); - // if has a description and if such gas is not already present - if (!gas.isEmpty() && gases.indexOf(gas) == -1) { - if (!gases.isEmpty()) - gases += QString(" / "); - gases += gas; - } - } - return gases; -} - -QString DiveObjectHelper::sac() const -{ - if (!m_dive->sac) - return QString(); - const char *unit; - int decimal; - double value = get_volume_units(m_dive->sac, &decimal, &unit); - return QString::number(value, 'f', decimal).append(unit); -} - -QString DiveObjectHelper::weightList() const -{ - QString weights; - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) { - QString w = getFormattedWeight(m_dive, i); - if (w == EMPTY_DIVE_STRING) - continue; - weights += w + "; "; - } - return weights; -} - -QStringList DiveObjectHelper::weights() const -{ - QStringList weights; - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) - weights << getFormattedWeight(m_dive, i); - return weights; -} - -bool DiveObjectHelper::singleWeight() const -{ - return weightsystem_none(&m_dive->weightsystem[1]); -} - -QString DiveObjectHelper::weight(int idx) const -{ - if ( (idx < 0) || idx > MAX_WEIGHTSYSTEMS ) - return QString(EMPTY_DIVE_STRING); - return getFormattedWeight(m_dive, idx); -} - -QString DiveObjectHelper::suit() const -{ - return m_dive->suit ? m_dive->suit : EMPTY_DIVE_STRING; -} - -QString DiveObjectHelper::cylinderList() const -{ - QString cylinders; - for (int i = 0; i < MAX_CYLINDERS; i++) { - QString cyl = getFormattedCylinder(m_dive, i); - if (cyl == EMPTY_DIVE_STRING) - continue; - cylinders += cyl + "; "; - } - return cylinders; -} - -QStringList DiveObjectHelper::cylinders() const -{ - QStringList cylinders; - for (int i = 0; i < MAX_CYLINDERS; i++) - cylinders << getFormattedCylinder(m_dive, i); - return cylinders; -} - -QString DiveObjectHelper::cylinder(int idx) const -{ - if ( (idx < 0) || idx > MAX_CYLINDERS) - return QString(EMPTY_DIVE_STRING); - return getFormattedCylinder(m_dive, idx); -} - -QString DiveObjectHelper::trip() const -{ - return m_dive->divetrip ? m_dive->divetrip->location : EMPTY_DIVE_STRING; -} - -// combine the pointer address with the trip location so that -// we detect multiple, destinct trips to the same location -QString DiveObjectHelper::tripMeta() const -{ - QString ret = EMPTY_DIVE_STRING; - if (m_dive->divetrip) - ret = QString::number((quint64)m_dive->divetrip, 16) + QLatin1Literal("::") + m_dive->divetrip->location; - return ret; -} - -QString DiveObjectHelper::maxcns() const -{ - return QString(m_dive->maxcns); -} - -QString DiveObjectHelper::otu() const -{ - return QString(m_dive->otu); -} - -int DiveObjectHelper::rating() const -{ - return m_dive->rating; -} - -QString DiveObjectHelper::sumWeight() const -{ - weight_t sum = { 0 }; - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++){ - sum.grams += m_dive->weightsystem[i].weight.grams; - } - return get_weight_string(sum, true); -} - -QString DiveObjectHelper::getCylinder() const -{ - QString getCylinder; - if (is_cylinder_used(m_dive, 1)){ - getCylinder = QObject::tr("Multiple"); - } - else { - getCylinder = m_dive->cylinder[0].type.description; - } - return getCylinder; -} - -QString DiveObjectHelper::startPressure() const -{ - QString startPressure = getPressures(m_dive, START_PRESSURE); - return startPressure; -} - -QString DiveObjectHelper::endPressure() const -{ - QString endPressure = getPressures(m_dive, END_PRESSURE); - return endPressure; -} - -QString DiveObjectHelper::firstGas() const -{ - QString gas; - gas = get_gas_string(m_dive->cylinder[0].gasmix); - return gas; -} diff --git a/subsurface-core/subsurface-qt/DiveObjectHelper.h b/subsurface-core/subsurface-qt/DiveObjectHelper.h deleted file mode 100644 index 602775ef8..000000000 --- a/subsurface-core/subsurface-qt/DiveObjectHelper.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef DIVE_QOBJECT_H -#define DIVE_QOBJECT_H - -#include "../dive.h" -#include <QObject> -#include <QString> -#include <QStringList> - -class DiveObjectHelper : public QObject { - Q_OBJECT - Q_PROPERTY(int number READ number CONSTANT) - Q_PROPERTY(int id READ id CONSTANT) - Q_PROPERTY(int rating READ rating CONSTANT) - Q_PROPERTY(QString date READ date CONSTANT) - Q_PROPERTY(QString time READ time CONSTANT) - Q_PROPERTY(QString location READ location CONSTANT) - Q_PROPERTY(QString gps READ gps CONSTANT) - Q_PROPERTY(QString duration READ duration CONSTANT) - Q_PROPERTY(bool noDive READ noDive CONSTANT) - Q_PROPERTY(QString depth READ depth CONSTANT) - Q_PROPERTY(QString divemaster READ divemaster CONSTANT) - Q_PROPERTY(QString buddy READ buddy CONSTANT) - Q_PROPERTY(QString airTemp READ airTemp CONSTANT) - Q_PROPERTY(QString waterTemp READ waterTemp CONSTANT) - Q_PROPERTY(QString notes READ notes CONSTANT) - Q_PROPERTY(QString tags READ tags CONSTANT) - Q_PROPERTY(QString gas READ gas CONSTANT) - Q_PROPERTY(QString sac READ sac CONSTANT) - Q_PROPERTY(QString weightList READ weightList CONSTANT) - Q_PROPERTY(QStringList weights READ weights CONSTANT) - Q_PROPERTY(bool singleWeight READ singleWeight CONSTANT) - Q_PROPERTY(QString suit READ suit CONSTANT) - Q_PROPERTY(QString cylinderList READ cylinderList CONSTANT) - Q_PROPERTY(QStringList cylinders READ cylinders CONSTANT) - Q_PROPERTY(QString trip READ trip CONSTANT) - Q_PROPERTY(QString tripMeta READ tripMeta CONSTANT) - Q_PROPERTY(QString maxcns READ maxcns CONSTANT) - Q_PROPERTY(QString otu READ otu CONSTANT) - Q_PROPERTY(QString sumWeight READ sumWeight CONSTANT) - Q_PROPERTY(QString getCylinder READ getCylinder CONSTANT) - Q_PROPERTY(QString startPressure READ startPressure CONSTANT) - Q_PROPERTY(QString endPressure READ endPressure CONSTANT) - Q_PROPERTY(QString firstGas READ firstGas CONSTANT) -public: - DiveObjectHelper(struct dive *dive = NULL); - ~DiveObjectHelper(); - int number() const; - int id() const; - int rating() const; - QString date() const; - timestamp_t timestamp() const; - QString time() const; - QString location() const; - QString gps() const; - QString duration() const; - bool noDive() const; - QString depth() const; - QString divemaster() const; - QString buddy() const; - QString airTemp() const; - QString waterTemp() const; - QString notes() const; - QString tags() const; - QString gas() const; - QString sac() const; - QString weightList() const; - QStringList weights() const; - QString weight(int idx) const; - bool singleWeight() const; - QString suit() const; - QString cylinderList() const; - QStringList cylinders() const; - QString cylinder(int idx) const; - QString trip() const; - QString tripMeta() const; - QString maxcns() const; - QString otu() const; - QString sumWeight() const; - QString getCylinder() const; - QString startPressure() const; - QString endPressure() const; - QString firstGas() const; - -private: - struct dive *m_dive; -}; - Q_DECLARE_METATYPE(DiveObjectHelper *) - -#endif diff --git a/subsurface-core/subsurface-qt/SettingsObjectWrapper.cpp b/subsurface-core/subsurface-qt/SettingsObjectWrapper.cpp deleted file mode 100644 index e43be1a9b..000000000 --- a/subsurface-core/subsurface-qt/SettingsObjectWrapper.cpp +++ /dev/null @@ -1,1617 +0,0 @@ -#include "SettingsObjectWrapper.h" -#include <QSettings> -#include <QApplication> -#include <QFont> - -#include "../dive.h" // TODO: remove copy_string from dive.h - - -static QString tecDetails = QStringLiteral("TecDetails"); - -PartialPressureGasSettings::PartialPressureGasSettings(QObject* parent): - QObject(parent), - group("TecDetails") -{ - -} - -short PartialPressureGasSettings::showPo2() const -{ - return prefs.pp_graphs.po2; -} - -short PartialPressureGasSettings::showPn2() const -{ - return prefs.pp_graphs.pn2; -} - -short PartialPressureGasSettings::showPhe() const -{ - return prefs.pp_graphs.phe; -} - -double PartialPressureGasSettings::po2Threshold() const -{ - return prefs.pp_graphs.po2_threshold; -} - -double PartialPressureGasSettings::pn2Threshold() const -{ - return prefs.pp_graphs.pn2_threshold; -} - -double PartialPressureGasSettings::pheThreshold() const -{ - return prefs.pp_graphs.phe_threshold; -} - -void PartialPressureGasSettings::setShowPo2(short value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("po2graph", value); - prefs.pp_graphs.po2 = value; - emit showPo2Changed(value); -} - -void PartialPressureGasSettings::setShowPn2(short value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("pn2graph", value); - prefs.pp_graphs.pn2 = value; - emit showPn2Changed(value); -} - -void PartialPressureGasSettings::setShowPhe(short value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("phegraph", value); - prefs.pp_graphs.phe = value; - emit showPheChanged(value); -} - -void PartialPressureGasSettings::setPo2Threshold(double value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("po2threshold", value); - prefs.pp_graphs.po2_threshold = value; - emit po2ThresholdChanged(value); -} - -void PartialPressureGasSettings::setPn2Threshold(double value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("pn2threshold", value); - prefs.pp_graphs.pn2_threshold = value; - emit pn2ThresholdChanged(value); -} - -void PartialPressureGasSettings::setPheThreshold(double value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("phethreshold", value); - prefs.pp_graphs.phe_threshold = value; - emit pheThresholdChanged(value); -} - - -TechnicalDetailsSettings::TechnicalDetailsSettings(QObject* parent): QObject(parent) -{ - -} - -double TechnicalDetailsSettings:: modp02() const -{ - return prefs.modpO2; -} - -bool TechnicalDetailsSettings::ead() const -{ - return prefs.ead; -} - -bool TechnicalDetailsSettings::dcceiling() const -{ - return prefs.dcceiling; -} - -bool TechnicalDetailsSettings::redceiling() const -{ - return prefs.redceiling; -} - -bool TechnicalDetailsSettings::calcceiling() const -{ - return prefs.calcceiling; -} - -bool TechnicalDetailsSettings::calcceiling3m() const -{ - return prefs.calcceiling3m; -} - -bool TechnicalDetailsSettings::calcalltissues() const -{ - return prefs.calcalltissues; -} - -bool TechnicalDetailsSettings::calcndltts() const -{ - return prefs.calcndltts; -} - -bool TechnicalDetailsSettings::gflow() const -{ - return prefs.gflow; -} - -bool TechnicalDetailsSettings::gfhigh() const -{ - return prefs.gfhigh; -} - -bool TechnicalDetailsSettings::hrgraph() const -{ - return prefs.hrgraph; -} - -bool TechnicalDetailsSettings::tankBar() const -{ - return prefs.tankbar; -} - -bool TechnicalDetailsSettings::percentageGraph() const -{ - return prefs.percentagegraph; -} - -bool TechnicalDetailsSettings::rulerGraph() const -{ - return prefs.rulergraph; -} - -bool TechnicalDetailsSettings::showCCRSetpoint() const -{ - return prefs.show_ccr_setpoint; -} - -bool TechnicalDetailsSettings::showCCRSensors() const -{ - return prefs.show_ccr_sensors; -} - -bool TechnicalDetailsSettings::zoomedPlot() const -{ - return prefs.zoomed_plot; -} - -bool TechnicalDetailsSettings::showSac() const -{ - return prefs.show_sac; -} - -bool TechnicalDetailsSettings::gfLowAtMaxDepth() const -{ - return prefs.gf_low_at_maxdepth; -} - -bool TechnicalDetailsSettings::displayUnusedTanks() const -{ - return prefs.display_unused_tanks; -} - -bool TechnicalDetailsSettings::showAverageDepth() const -{ - return prefs.show_average_depth; -} - -bool TechnicalDetailsSettings::mod() const -{ - return prefs.mod; -} - -bool TechnicalDetailsSettings::showPicturesInProfile() const -{ - return prefs.show_pictures_in_profile; -} - -void TechnicalDetailsSettings::setModp02(double value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("modpO2", value); - prefs.modpO2 = value; - emit modpO2Changed(value); -} - -void TechnicalDetailsSettings::setShowPicturesInProfile(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("show_pictures_in_profile", value); - prefs.show_pictures_in_profile = value; - emit showPicturesInProfileChanged(value); -} - -void TechnicalDetailsSettings::setEad(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("ead", value); - prefs.ead = value; - emit eadChanged(value); -} - -void TechnicalDetailsSettings::setMod(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("mod", value); - prefs.mod = value; - emit modChanged(value); -} - -void TechnicalDetailsSettings::setDCceiling(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("dcceiling", value); - prefs.dcceiling = value; - emit dcceilingChanged(value); -} - -void TechnicalDetailsSettings::setRedceiling(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("redceiling", value); - prefs.redceiling = value; - emit redceilingChanged(value); -} - -void TechnicalDetailsSettings::setCalcceiling(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("calcceiling", value); - prefs.calcceiling = value; - emit calcceilingChanged(value); -} - -void TechnicalDetailsSettings::setCalcceiling3m(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("calcceiling3m", value); - prefs.calcceiling3m = value; - emit calcceiling3mChanged(value); -} - -void TechnicalDetailsSettings::setCalcalltissues(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("calcalltissues", value); - prefs.calcalltissues = value; - emit calcalltissuesChanged(value); -} - -void TechnicalDetailsSettings::setCalcndltts(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("calcndltts", value); - prefs.calcndltts = value; - emit calcndlttsChanged(value); -} - -void TechnicalDetailsSettings::setGflow(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("gflow", value); - prefs.gflow = value; - set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); - emit gflowChanged(value); -} - -void TechnicalDetailsSettings::setGfhigh(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("gfhigh", value); - prefs.gfhigh = value; - set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); - emit gfhighChanged(value); -} - -void TechnicalDetailsSettings::setHRgraph(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("hrgraph", value); - prefs.hrgraph = value; - emit hrgraphChanged(value); -} - -void TechnicalDetailsSettings::setTankBar(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("tankbar", value); - prefs.tankbar = value; - emit tankBarChanged(value); -} - -void TechnicalDetailsSettings::setPercentageGraph(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("percentagegraph", value); - prefs.percentagegraph = value; - emit percentageGraphChanged(value); -} - -void TechnicalDetailsSettings::setRulerGraph(bool value) -{ - /* TODO: search for the QSettings of the RulerBar */ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("RulerBar", value); - prefs.rulergraph = value; - emit rulerGraphChanged(value); -} - -void TechnicalDetailsSettings::setShowCCRSetpoint(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("show_ccr_setpoint", value); - prefs.show_ccr_setpoint = value; - emit showCCRSetpointChanged(value); -} - -void TechnicalDetailsSettings::setShowCCRSensors(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("show_ccr_sensors", value); - prefs.show_ccr_sensors = value; - emit showCCRSensorsChanged(value); -} - -void TechnicalDetailsSettings::setZoomedPlot(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("zoomed_plot", value); - prefs.zoomed_plot = value; - emit zoomedPlotChanged(value); -} - -void TechnicalDetailsSettings::setShowSac(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("show_sac", value); - prefs.show_sac = value; - emit showSacChanged(value); -} - -void TechnicalDetailsSettings::setGfLowAtMaxDepth(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("gf_low_at_maxdepth", value); - prefs.gf_low_at_maxdepth = value; - set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); - emit gfLowAtMaxDepthChanged(value); -} - -void TechnicalDetailsSettings::setDisplayUnusedTanks(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("display_unused_tanks", value); - prefs.display_unused_tanks = value; - emit displayUnusedTanksChanged(value); -} - -void TechnicalDetailsSettings::setShowAverageDepth(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("show_average_depth", value); - prefs.show_average_depth = value; - emit showAverageDepthChanged(value); -} - - - -FacebookSettings::FacebookSettings(QObject *parent) : - QObject(parent), - group(QStringLiteral("WebApps")), - subgroup(QStringLiteral("Facebook")) -{ -} - -QString FacebookSettings::accessToken() const -{ - return QString(prefs.facebook.access_token); -} - -QString FacebookSettings::userId() const -{ - return QString(prefs.facebook.user_id); -} - -QString FacebookSettings::albumId() const -{ - return QString(prefs.facebook.album_id); -} - -void FacebookSettings::setAccessToken (const QString& value) -{ -#if SAVE_FB_CREDENTIALS - QSettings s; - s.beginGroup(group); - s.beginGroup(subgroup); - s.setValue("ConnectToken", value); -#endif - prefs.facebook.access_token = copy_string(qPrintable(value)); - emit accessTokenChanged(value); -} - -void FacebookSettings::setUserId(const QString& value) -{ -#if SAVE_FB_CREDENTIALS - QSettings s; - s.beginGroup(group); - s.beginGroup(subgroup); - s.setValue("UserId", value); -#endif - prefs.facebook.user_id = copy_string(qPrintable(value)); - emit userIdChanged(value); -} - -void FacebookSettings::setAlbumId(const QString& value) -{ -#if SAVE_FB_CREDENTIALS - QSettings s; - s.beginGroup(group); - s.beginGroup(subgroup); - s.setValue("AlbumId", value); -#endif - prefs.facebook.album_id = copy_string(qPrintable(value)); - emit albumIdChanged(value); -} - - -GeocodingPreferences::GeocodingPreferences(QObject *parent) : - QObject(parent), - group(QStringLiteral("geocoding")) -{ - -} - -bool GeocodingPreferences::enableGeocoding() const -{ - return prefs.geocoding.enable_geocoding; -} - -bool GeocodingPreferences::parseDiveWithoutGps() const -{ - return prefs.geocoding.parse_dive_without_gps; -} - -bool GeocodingPreferences::tagExistingDives() const -{ - return prefs.geocoding.tag_existing_dives; -} - -taxonomy_category GeocodingPreferences::firstTaxonomyCategory() const -{ - return prefs.geocoding.category[0]; -} - -taxonomy_category GeocodingPreferences::secondTaxonomyCategory() const -{ - return prefs.geocoding.category[1]; -} - -taxonomy_category GeocodingPreferences::thirdTaxonomyCategory() const -{ - return prefs.geocoding.category[2]; -} - -void GeocodingPreferences::setEnableGeocoding(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("enable_geocoding", value); - prefs.geocoding.enable_geocoding = value; - emit enableGeocodingChanged(value); -} - -void GeocodingPreferences::setParseDiveWithoutGps(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("parse_dives_without_gps", value); - prefs.geocoding.parse_dive_without_gps = value; - emit parseDiveWithoutGpsChanged(value); -} - -void GeocodingPreferences::setTagExistingDives(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("tag_existing_dives", value); - prefs.geocoding.tag_existing_dives = value; - emit tagExistingDivesChanged(value); -} - -void GeocodingPreferences::setFirstTaxonomyCategory(taxonomy_category value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("cat0", value); - prefs.show_average_depth = value; - emit firstTaxonomyCategoryChanged(value); -} - -void GeocodingPreferences::setSecondTaxonomyCategory(taxonomy_category value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("cat1", value); - prefs.show_average_depth = value; - emit secondTaxonomyCategoryChanged(value); -} - -void GeocodingPreferences::setThirdTaxonomyCategory(taxonomy_category value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("cat2", value); - prefs.show_average_depth = value; - emit thirdTaxonomyCategoryChanged(value); -} - -ProxySettings::ProxySettings(QObject *parent) : - QObject(parent), - group(QStringLiteral("Network")) -{ -} - -int ProxySettings::type() const -{ - return prefs.proxy_type; -} - -QString ProxySettings::host() const -{ - return prefs.proxy_host; -} - -int ProxySettings::port() const -{ - return prefs.proxy_port; -} - -short ProxySettings::auth() const -{ - return prefs.proxy_auth; -} - -QString ProxySettings::user() const -{ - return prefs.proxy_user; -} - -QString ProxySettings::pass() const -{ - return prefs.proxy_pass; -} - -void ProxySettings::setType(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_type", value); - prefs.proxy_type = value; - emit typeChanged(value); -} - -void ProxySettings::setHost(const QString& value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_host", value); - free(prefs.proxy_host); - prefs.proxy_host = copy_string(qPrintable(value));; - emit hostChanged(value); -} - -void ProxySettings::setPort(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_port", value); - prefs.proxy_port = value; - emit portChanged(value); -} - -void ProxySettings::setAuth(short value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_auth", value); - prefs.proxy_auth = value; - emit authChanged(value); -} - -void ProxySettings::setUser(const QString& value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_user", value); - free(prefs.proxy_user); - prefs.proxy_user = copy_string(qPrintable(value)); - emit userChanged(value); -} - -void ProxySettings::setPass(const QString& value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_pass", value); - free(prefs.proxy_pass); - prefs.proxy_pass = copy_string(qPrintable(value)); - emit passChanged(value); -} - -CloudStorageSettings::CloudStorageSettings(QObject *parent) : - QObject(parent), - group(QStringLiteral("CloudStorage")) -{ - -} - -bool CloudStorageSettings::gitLocalOnly() const -{ - return prefs.git_local_only; -} - -QString CloudStorageSettings::password() const -{ - return QString(prefs.cloud_storage_password); -} - -QString CloudStorageSettings::newPassword() const -{ - return QString(prefs.cloud_storage_newpassword); -} - -QString CloudStorageSettings::email() const -{ - return QString(prefs.cloud_storage_email); -} - -QString CloudStorageSettings::emailEncoded() const -{ - return QString(prefs.cloud_storage_email_encoded); -} - -bool CloudStorageSettings::savePasswordLocal() const -{ - return prefs.save_password_local; -} - -short CloudStorageSettings::verificationStatus() const -{ - return prefs.cloud_verification_status; -} - -bool CloudStorageSettings::backgroundSync() const -{ - return prefs.cloud_background_sync; -} - -QString CloudStorageSettings::userId() const -{ - return QString(prefs.userid); -} - -QString CloudStorageSettings::baseUrl() const -{ - return QString(prefs.cloud_base_url); -} - -QString CloudStorageSettings::gitUrl() const -{ - return QString(prefs.cloud_git_url); -} - -void CloudStorageSettings::setPassword(const QString& value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("password", value); - free(prefs.proxy_pass); - prefs.proxy_pass = copy_string(qPrintable(value)); - emit passwordChanged(value); -} - -void CloudStorageSettings::setNewPassword(const QString& value) -{ - /*TODO: This looks like wrong, but 'new password' is not saved on disk, why it's on prefs? */ - free(prefs.cloud_storage_newpassword); - prefs.cloud_storage_newpassword = copy_string(qPrintable(value)); - emit newPasswordChanged(value); -} - -void CloudStorageSettings::setEmail(const QString& value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("email", value); - free(prefs.cloud_storage_email); - prefs.cloud_storage_email = copy_string(qPrintable(value)); - emit emailChanged(value); -} - -void CloudStorageSettings::setUserId(const QString& value) -{ - //WARNING: UserId is stored outside of any group, but it belongs to Cloud Storage. - QSettings s; - s.setValue("subsurface_webservice_uid", value); - free(prefs.userid); - prefs.userid = copy_string(qPrintable(value)); - emit userIdChanged(value); -} - -void CloudStorageSettings::setEmailEncoded(const QString& value) -{ - /*TODO: This looks like wrong, but 'email encoded' is not saved on disk, why it's on prefs? */ - free(prefs.cloud_storage_email_encoded); - prefs.cloud_storage_email_encoded = copy_string(qPrintable(value)); - emit emailEncodedChanged(value); -} - -void CloudStorageSettings::setSavePasswordLocal(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("save_password_local", value); - prefs.save_password_local = value; - emit savePasswordLocalChanged(value); -} - -void CloudStorageSettings::setVerificationStatus(short value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("cloud_verification_status", value); - prefs.cloud_verification_status = value; - emit verificationStatusChanged(value); -} - -void CloudStorageSettings::setBackgroundSync(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("cloud_background_sync", value); - prefs.cloud_background_sync = value; - emit backgroundSyncChanged(value); -} - -void CloudStorageSettings::setBaseUrl(const QString& value) -{ - free((void*)prefs.cloud_base_url); - free((void*)prefs.cloud_git_url); - prefs.cloud_base_url = copy_string(qPrintable(value)); - prefs.cloud_git_url = copy_string(qPrintable(QString(prefs.cloud_base_url) + "/git")); -} - -void CloudStorageSettings::setGitUrl(const QString& value) -{ - Q_UNUSED(value); /* no op */ -} - -void CloudStorageSettings::setGitLocalOnly(bool value) -{ - prefs.git_local_only = value; -} - -DivePlannerSettings::DivePlannerSettings(QObject *parent) : - QObject(parent), - group(QStringLiteral("Planner")) -{ -} - -bool DivePlannerSettings::lastStop() const -{ - return prefs.last_stop; -} - -bool DivePlannerSettings::verbatimPlan() const -{ - return prefs.verbatim_plan; -} - -bool DivePlannerSettings::displayRuntime() const -{ - return prefs.display_runtime; -} - -bool DivePlannerSettings::displayDuration() const -{ - return prefs.display_duration; -} - -bool DivePlannerSettings::displayTransitions() const -{ - return prefs.display_transitions; -} - -bool DivePlannerSettings::doo2breaks() const -{ - return prefs.doo2breaks; -} - -bool DivePlannerSettings::dropStoneMode() const -{ - return prefs.drop_stone_mode; -} - -bool DivePlannerSettings::safetyStop() const -{ - return prefs.safetystop; -} - -bool DivePlannerSettings::switchAtRequiredStop() const -{ - return prefs.switch_at_req_stop; -} - -int DivePlannerSettings::ascrate75() const -{ - return prefs.ascrate75; -} - -int DivePlannerSettings::ascrate50() const -{ - return prefs.ascrate50; -} - -int DivePlannerSettings::ascratestops() const -{ - return prefs.ascratestops; -} - -int DivePlannerSettings::ascratelast6m() const -{ - return prefs.ascratelast6m; -} - -int DivePlannerSettings::descrate() const -{ - return prefs.descrate; -} - -int DivePlannerSettings::bottompo2() const -{ - return prefs.bottompo2; -} - -int DivePlannerSettings::decopo2() const -{ - return prefs.decopo2; -} - -int DivePlannerSettings::reserveGas() const -{ - return prefs.reserve_gas; -} - -int DivePlannerSettings::minSwitchDuration() const -{ - return prefs.min_switch_duration; -} - -int DivePlannerSettings::bottomSac() const -{ - return prefs.bottomsac; -} - -int DivePlannerSettings::decoSac() const -{ - return prefs.decosac; -} - -short DivePlannerSettings::conservatismLevel() const -{ - return prefs.conservatism_level; -} - -deco_mode DivePlannerSettings::decoMode() const -{ - return prefs.deco_mode; -} - -void DivePlannerSettings::setLastStop(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("last_stop", value); - prefs.last_stop = value; - emit lastStopChanged(value); -} - -void DivePlannerSettings::setVerbatimPlan(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("verbatim_plan", value); - prefs.verbatim_plan = value; - emit verbatimPlanChanged(value); -} - -void DivePlannerSettings::setDisplayRuntime(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("display_runtime", value); - prefs.display_runtime = value; - emit displayRuntimeChanged(value); -} - -void DivePlannerSettings::setDisplayDuration(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("display_duration", value); - prefs.display_duration = value; - emit displayDurationChanged(value); -} - -void DivePlannerSettings::setDisplayTransitions(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("display_transitions", value); - prefs.display_transitions = value; - emit displayTransitionsChanged(value); -} - -void DivePlannerSettings::setDoo2breaks(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("doo2breaks", value); - prefs.doo2breaks = value; - emit doo2breaksChanged(value); -} - -void DivePlannerSettings::setDropStoneMode(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("drop_stone_mode", value); - prefs.drop_stone_mode = value; - emit dropStoneModeChanged(value); -} - -void DivePlannerSettings::setSafetyStop(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("safetystop", value); - prefs.safetystop = value; - emit safetyStopChanged(value); -} - -void DivePlannerSettings::setSwitchAtRequiredStop(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("switch_at_req_stop", value); - prefs.switch_at_req_stop = value; - emit switchAtRequiredStopChanged(value); -} - -void DivePlannerSettings::setAscrate75(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("ascrate75", value); - prefs.ascrate75 = value; - emit ascrate75Changed(value); -} - -void DivePlannerSettings::setAscrate50(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("ascrate50", value); - prefs.ascrate50 = value; - emit ascrate50Changed(value); -} - -void DivePlannerSettings::setAscratestops(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("ascratestops", value); - prefs.ascratestops = value; - emit ascratestopsChanged(value); -} - -void DivePlannerSettings::setAscratelast6m(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("ascratelast6m", value); - prefs.ascratelast6m = value; - emit ascratelast6mChanged(value); -} - -void DivePlannerSettings::setDescrate(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("descrate", value); - prefs.descrate = value; - emit descrateChanged(value); -} - -void DivePlannerSettings::setBottompo2(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("bottompo2", value); - prefs.bottompo2 = value; - emit bottompo2Changed(value); -} - -void DivePlannerSettings::setDecopo2(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("decopo2", value); - prefs.decopo2 = value; - emit decopo2Changed(value); -} - -void DivePlannerSettings::setReserveGas(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("reserve_gas", value); - prefs.reserve_gas = value; - emit reserveGasChanged(value); -} - -void DivePlannerSettings::setMinSwitchDuration(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("min_switch_duration", value); - prefs.min_switch_duration = value; - emit minSwitchDurationChanged(value); -} - -void DivePlannerSettings::setBottomSac(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("bottomsac", value); - prefs.bottomsac = value; - emit bottomSacChanged(value); -} - -void DivePlannerSettings::setSecoSac(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("decosac", value); - prefs.decosac = value; - emit decoSacChanged(value); -} - -void DivePlannerSettings::setConservatismLevel(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("conservatism", value); - prefs.conservatism_level = value; - emit conservatismLevelChanged(value); -} - -void DivePlannerSettings::setDecoMode(deco_mode value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("deco_mode", value); - prefs.deco_mode = value; - emit decoModeChanged(value); -} - -UnitsSettings::UnitsSettings(QObject *parent) : - QObject(parent), - group(QStringLiteral("Units")) -{ - -} - -int UnitsSettings::length() const -{ - return prefs.units.length; -} - -int UnitsSettings::pressure() const -{ - return prefs.units.pressure; -} - -int UnitsSettings::volume() const -{ - return prefs.units.volume; -} - -int UnitsSettings::temperature() const -{ - return prefs.units.temperature; -} - -int UnitsSettings::weight() const -{ - return prefs.units.weight; -} - -int UnitsSettings::verticalSpeedTime() const -{ - return prefs.units.vertical_speed_time; -} - -QString UnitsSettings::unitSystem() const -{ - return QString(); /*FIXME: there's no char * units on the prefs. */ -} - -bool UnitsSettings::coordinatesTraditional() const -{ - return prefs.coordinates_traditional; -} - -void UnitsSettings::setLength(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("length", value); - prefs.units.length = (units::LENGHT) value; - emit lengthChanged(value); -} - -void UnitsSettings::setPressure(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("pressure", value); - prefs.units.pressure = (units::PRESSURE) value; - emit pressureChanged(value); -} - -void UnitsSettings::setVolume(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("volume", value); - prefs.units.volume = (units::VOLUME) value; - emit volumeChanged(value); -} - -void UnitsSettings::setTemperature(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("temperature", value); - prefs.units.temperature = (units::TEMPERATURE) value; - emit temperatureChanged(value); -} - -void UnitsSettings::setWeight(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("weight", value); - prefs.units.weight = (units::WEIGHT) value; - emit weightChanged(value); -} - -void UnitsSettings::setVerticalSpeedTime(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("vertical_speed_time", value); - prefs.units.vertical_speed_time = (units::TIME) value; - emit verticalSpeedTimeChanged(value); -} - -void UnitsSettings::setCoordinatesTraditional(bool value) -{ - QSettings s; - s.setValue("coordinates", value); - prefs.coordinates_traditional = value; - emit coordinatesTraditionalChanged(value); -} - -void UnitsSettings::setUnitSystem(const QString& value) -{ - QSettings s; - s.setValue("unit_system", value); - - if (value == QStringLiteral("metric")) { - prefs.unit_system = METRIC; - prefs.units = SI_units; - } else if (value == QStringLiteral("imperial")) { - prefs.unit_system = IMPERIAL; - prefs.units = IMPERIAL_units; - } else { - prefs.unit_system = PERSONALIZE; - } - - emit unitSystemChanged(value); - // TODO: emit the other values here? -} - -GeneralSettingsObjectWrapper::GeneralSettingsObjectWrapper(QObject *parent) : - QObject(parent), - group(QStringLiteral("GeneralSettings")) -{ -} - -QString GeneralSettingsObjectWrapper::defaultFilename() const -{ - return prefs.default_filename; -} - -QString GeneralSettingsObjectWrapper::defaultCylinder() const -{ - return prefs.default_cylinder; -} - -short GeneralSettingsObjectWrapper::defaultFileBehavior() const -{ - return prefs.default_file_behavior; -} - -bool GeneralSettingsObjectWrapper::useDefaultFile() const -{ - return prefs.use_default_file; -} - -int GeneralSettingsObjectWrapper::defaultSetPoint() const -{ - return prefs.defaultsetpoint; -} - -int GeneralSettingsObjectWrapper::o2Consumption() const -{ - return prefs.o2consumption; -} - -int GeneralSettingsObjectWrapper::pscrRatio() const -{ - return prefs.pscr_ratio; -} - -void GeneralSettingsObjectWrapper::setDefaultFilename(const QString& value) -{ - QSettings s; - s.setValue("default_filename", value); - prefs.default_filename = copy_string(qPrintable(value)); - emit defaultFilenameChanged(value); -} - -void GeneralSettingsObjectWrapper::setDefaultCylinder(const QString& value) -{ - QSettings s; - s.setValue("default_cylinder", value); - prefs.default_cylinder = copy_string(qPrintable(value)); - emit defaultCylinderChanged(value); -} - -void GeneralSettingsObjectWrapper::setDefaultFileBehavior(short value) -{ - QSettings s; - s.setValue("default_file_behavior", value); - prefs.default_file_behavior = value; - if (prefs.default_file_behavior == UNDEFINED_DEFAULT_FILE) { - // undefined, so check if there's a filename set and - // use that, otherwise go with no default file - if (QString(prefs.default_filename).isEmpty()) - prefs.default_file_behavior = NO_DEFAULT_FILE; - else - prefs.default_file_behavior = LOCAL_DEFAULT_FILE; - } - emit defaultFileBehaviorChanged(value); -} - -void GeneralSettingsObjectWrapper::setUseDefaultFile(bool value) -{ - QSettings s; - s.setValue("use_default_file", value); - prefs.use_default_file = value; - emit useDefaultFileChanged(value); -} - -void GeneralSettingsObjectWrapper::setDefaultSetPoint(int value) -{ - QSettings s; - s.setValue("defaultsetpoint", value); - prefs.defaultsetpoint = value; - emit defaultSetPointChanged(value); -} - -void GeneralSettingsObjectWrapper::setO2Consumption(int value) -{ - QSettings s; - s.setValue("o2consumption", value); - prefs.o2consumption = value; - emit o2ConsumptionChanged(value); -} - -void GeneralSettingsObjectWrapper::setPscrRatio(int value) -{ - QSettings s; - s.setValue("pscr_ratio", value); - prefs.pscr_ratio = value; - emit pscrRatioChanged(value); -} - -DisplaySettingsObjectWrapper::DisplaySettingsObjectWrapper(QObject *parent) : - QObject(parent), - group(QStringLiteral("Display")) -{ -} - -QString DisplaySettingsObjectWrapper::divelistFont() const -{ - return prefs.divelist_font; -} - -double DisplaySettingsObjectWrapper::fontSize() const -{ - return prefs.font_size; -} - -short DisplaySettingsObjectWrapper::displayInvalidDives() const -{ - return prefs.display_invalid_dives; -} - -void DisplaySettingsObjectWrapper::setDivelistFont(const QString& value) -{ - QSettings s; - s.setValue("divelist_font", value); - QString newValue = value; - if (value.contains(",")) - newValue = value.left(value.indexOf(",")); - - if (!subsurface_ignore_font(newValue.toUtf8().constData())) { - free((void *)prefs.divelist_font); - prefs.divelist_font = strdup(newValue.toUtf8().constData()); - qApp->setFont(QFont(newValue)); - } - emit divelistFontChanged(newValue); -} - -void DisplaySettingsObjectWrapper::setFontSize(double value) -{ - QSettings s; - s.setValue("font_size", value); - prefs.font_size = value; - QFont defaultFont = qApp->font(); - defaultFont.setPointSizeF(prefs.font_size); - qApp->setFont(defaultFont); - emit fontSizeChanged(value); -} - -void DisplaySettingsObjectWrapper::setDisplayInvalidDives(short value) -{ - QSettings s; - s.setValue("displayinvalid", value); - prefs.display_invalid_dives = value; - emit displayInvalidDivesChanged(value); -} - -LanguageSettingsObjectWrapper::LanguageSettingsObjectWrapper(QObject *parent) : - QObject(parent), - group("Language") -{ -} - -QString LanguageSettingsObjectWrapper::language() const -{ - return prefs.locale.language; -} - -QString LanguageSettingsObjectWrapper::timeFormat() const -{ - return prefs.time_format; -} - -QString LanguageSettingsObjectWrapper::dateFormat() const -{ - return prefs.date_format; -} - -QString LanguageSettingsObjectWrapper::dateFormatShort() const -{ - return prefs.date_format_short; -} - -bool LanguageSettingsObjectWrapper::timeFormatOverride() const -{ - return prefs.time_format_override; -} - -bool LanguageSettingsObjectWrapper::dateFormatOverride() const -{ - return prefs.date_format_override; -} - -bool LanguageSettingsObjectWrapper::useSystemLanguage() const -{ - return prefs.locale.use_system_language; -} - -void LanguageSettingsObjectWrapper::setUseSystemLanguage(bool value) -{ - QSettings s; - s.setValue("UseSystemLanguage", value); - prefs.locale.use_system_language = copy_string(qPrintable(value)); - emit useSystemLanguageChanged(value); -} - -void LanguageSettingsObjectWrapper::setLanguage(const QString& value) -{ - QSettings s; - s.setValue("UiLanguage", value); - prefs.locale.language = copy_string(qPrintable(value)); - emit languageChanged(value); -} - -void LanguageSettingsObjectWrapper::setTimeFormat(const QString& value) -{ - QSettings s; - s.setValue("time_format", value); - prefs.time_format = copy_string(qPrintable(value));; - emit timeFormatChanged(value); -} - -void LanguageSettingsObjectWrapper::setDateFormat(const QString& value) -{ - QSettings s; - s.setValue("date_format", value); - prefs.date_format = copy_string(qPrintable(value));; - emit dateFormatChanged(value); -} - -void LanguageSettingsObjectWrapper::setDateFormatShort(const QString& value) -{ - QSettings s; - s.setValue("date_format_short", value); - prefs.date_format_short = copy_string(qPrintable(value));; - emit dateFormatShortChanged(value); -} - -void LanguageSettingsObjectWrapper::setTimeFormatOverride(bool value) -{ - QSettings s; - s.setValue("time_format_override", value); - prefs.time_format_override = value; - emit timeFormatOverrideChanged(value); -} - -void LanguageSettingsObjectWrapper::setDateFormatOverride(bool value) -{ - QSettings s; - s.setValue("date_format_override", value); - prefs.date_format_override = value; - emit dateFormatOverrideChanged(value); -} - -AnimationsSettingsObjectWrapper::AnimationsSettingsObjectWrapper(QObject* parent): - QObject(parent), - group("Animations") - -{ -} - -int AnimationsSettingsObjectWrapper::animationSpeed() const -{ - return prefs.animation_speed; -} - -void AnimationsSettingsObjectWrapper::setAnimationSpeed(int value) -{ - QSettings s; - s.setValue("animation_speed", value); - prefs.animation_speed = value; - emit animationSpeedChanged(value); -} - -LocationServiceSettingsObjectWrapper::LocationServiceSettingsObjectWrapper(QObject* parent): - QObject(parent), - group("locationService") -{ -} - -int LocationServiceSettingsObjectWrapper::distanceThreshold() const -{ - return prefs.distance_threshold; -} - -int LocationServiceSettingsObjectWrapper::timeThreshold() const -{ - return prefs.time_threshold; -} - -void LocationServiceSettingsObjectWrapper::setDistanceThreshold(int value) -{ - QSettings s; - s.setValue("distance_threshold", value); - prefs.distance_threshold = value; - emit distanceThresholdChanged(value); -} - -void LocationServiceSettingsObjectWrapper::setTimeThreshold(int value) -{ - QSettings s; - s.setValue("time_threshold", value); - prefs.time_threshold = value; - emit timeThresholdChanged( value); -} - -SettingsObjectWrapper::SettingsObjectWrapper(QObject* parent): -QObject(parent), - techDetails(new TechnicalDetailsSettings(this)), - pp_gas(new PartialPressureGasSettings(this)), - facebook(new FacebookSettings(this)), - geocoding(new GeocodingPreferences(this)), - proxy(new ProxySettings(this)), - cloud_storage(new CloudStorageSettings(this)), - planner_settings(new DivePlannerSettings(this)), - unit_settings(new UnitsSettings(this)), - general_settings(new GeneralSettingsObjectWrapper(this)), - display_settings(new DisplaySettingsObjectWrapper(this)), - language_settings(new LanguageSettingsObjectWrapper(this)), - animation_settings(new AnimationsSettingsObjectWrapper(this)), - location_settings(new LocationServiceSettingsObjectWrapper(this)) -{ -} - -void SettingsObjectWrapper::setSaveUserIdLocal(short int value) -{ - Q_UNUSED(value); - //TODO: Find where this is stored on the preferences. -} - -short int SettingsObjectWrapper::saveUserIdLocal() const -{ - return prefs.save_userid_local; -} - -SettingsObjectWrapper* SettingsObjectWrapper::instance() -{ - static SettingsObjectWrapper settings; - return &settings; -} diff --git a/subsurface-core/subsurface-qt/SettingsObjectWrapper.h b/subsurface-core/subsurface-qt/SettingsObjectWrapper.h deleted file mode 100644 index f115e2d86..000000000 --- a/subsurface-core/subsurface-qt/SettingsObjectWrapper.h +++ /dev/null @@ -1,642 +0,0 @@ -#ifndef SETTINGSOBJECTWRAPPER_H -#define SETTINGSOBJECTWRAPPER_H - -#include <QObject> - -#include "../pref.h" -#include "../prefs-macros.h" - -/* Wrapper class for the Settings. This will allow - * seamlessy integration of the settings with the QML - * and QWidget frontends. This class will be huge, since - * I need tons of properties, one for each option. */ - -/* Control the state of the Partial Pressure Graphs preferences */ -class PartialPressureGasSettings : public QObject { - Q_OBJECT - Q_PROPERTY(short show_po2 READ showPo2 WRITE setShowPo2 NOTIFY showPo2Changed) - Q_PROPERTY(short show_pn2 READ showPn2 WRITE setShowPn2 NOTIFY showPn2Changed) - Q_PROPERTY(short show_phe READ showPhe WRITE setShowPhe NOTIFY showPheChanged) - Q_PROPERTY(double po2_threshold READ po2Threshold WRITE setPo2Threshold NOTIFY po2ThresholdChanged) - Q_PROPERTY(double pn2_threshold READ pn2Threshold WRITE setPn2Threshold NOTIFY pn2ThresholdChanged) - Q_PROPERTY(double phe_threshold READ pheThreshold WRITE setPheThreshold NOTIFY pheThresholdChanged) - -public: - PartialPressureGasSettings(QObject *parent); - short showPo2() const; - short showPn2() const; - short showPhe() const; - double po2Threshold() const; - double pn2Threshold() const; - double pheThreshold() const; - -public slots: - void setShowPo2(short value); - void setShowPn2(short value); - void setShowPhe(short value); - void setPo2Threshold(double value); - void setPn2Threshold(double value); - void setPheThreshold(double value); - -signals: - void showPo2Changed(short value); - void showPn2Changed(short value); - void showPheChanged(short value); - void po2ThresholdChanged(double value); - void pn2ThresholdChanged(double value); - void pheThresholdChanged(double value); -private: - QString group; -}; - -class TechnicalDetailsSettings : public QObject { - Q_OBJECT - Q_PROPERTY(double modpO2 READ modp02 WRITE setModp02 NOTIFY modpO2Changed) - Q_PROPERTY(bool ead READ ead WRITE setEad NOTIFY eadChanged) - Q_PROPERTY(bool mod READ mod WRITE setMod NOTIFY modChanged); - Q_PROPERTY(bool dcceiling READ dcceiling WRITE setDCceiling NOTIFY dcceilingChanged) - Q_PROPERTY(bool redceiling READ redceiling WRITE setRedceiling NOTIFY redceilingChanged) - Q_PROPERTY(bool calcceiling READ calcceiling WRITE setCalcceiling NOTIFY calcceilingChanged) - Q_PROPERTY(bool calcceiling3m READ calcceiling3m WRITE setCalcceiling3m NOTIFY calcceiling3mChanged) - Q_PROPERTY(bool calcalltissues READ calcalltissues WRITE setCalcalltissues NOTIFY calcalltissuesChanged) - Q_PROPERTY(bool calcndltts READ calcndltts WRITE setCalcndltts NOTIFY calcndlttsChanged) - Q_PROPERTY(bool gflow READ gflow WRITE setGflow NOTIFY gflowChanged) - Q_PROPERTY(bool gfhigh READ gfhigh WRITE setGfhigh NOTIFY gfhighChanged) - Q_PROPERTY(bool hrgraph READ hrgraph WRITE setHRgraph NOTIFY hrgraphChanged) - Q_PROPERTY(bool tankbar READ tankBar WRITE setTankBar NOTIFY tankBarChanged) - Q_PROPERTY(bool percentagegraph READ percentageGraph WRITE setPercentageGraph NOTIFY percentageGraphChanged) - Q_PROPERTY(bool rulergraph READ rulerGraph WRITE setRulerGraph NOTIFY rulerGraphChanged) - Q_PROPERTY(bool show_ccr_setpoint READ showCCRSetpoint WRITE setShowCCRSetpoint NOTIFY showCCRSetpointChanged) - Q_PROPERTY(bool show_ccr_sensors READ showCCRSensors WRITE setShowCCRSensors NOTIFY showCCRSensorsChanged) - Q_PROPERTY(bool zoomed_plot READ zoomedPlot WRITE setZoomedPlot NOTIFY zoomedPlotChanged) - Q_PROPERTY(bool show_sac READ showSac WRITE setShowSac NOTIFY showSacChanged) - Q_PROPERTY(bool gf_low_at_maxdepth READ gfLowAtMaxDepth WRITE setGfLowAtMaxDepth NOTIFY gfLowAtMaxDepthChanged) - Q_PROPERTY(bool display_unused_tanks READ displayUnusedTanks WRITE setDisplayUnusedTanks NOTIFY displayUnusedTanksChanged) - Q_PROPERTY(bool show_average_depth READ showAverageDepth WRITE setShowAverageDepth NOTIFY showAverageDepthChanged) - Q_PROPERTY(bool show_pictures_in_profile READ showPicturesInProfile WRITE setShowPicturesInProfile NOTIFY showPicturesInProfileChanged) -public: - TechnicalDetailsSettings(QObject *parent); - - double modp02() const; - bool ead() const; - bool mod() const; - bool dcceiling() const; - bool redceiling() const; - bool calcceiling() const; - bool calcceiling3m() const; - bool calcalltissues() const; - bool calcndltts() const; - bool gflow() const; - bool gfhigh() const; - bool hrgraph() const; - bool tankBar() const; - bool percentageGraph() const; - bool rulerGraph() const; - bool showCCRSetpoint() const; - bool showCCRSensors() const; - bool zoomedPlot() const; - bool showSac() const; - bool gfLowAtMaxDepth() const; - bool displayUnusedTanks() const; - bool showAverageDepth() const; - bool showPicturesInProfile() const; - -public slots: - void setMod(bool value); - void setModp02(double value); - void setEad(bool value); - void setDCceiling(bool value); - void setRedceiling(bool value); - void setCalcceiling(bool value); - void setCalcceiling3m(bool value); - void setCalcalltissues(bool value); - void setCalcndltts(bool value); - void setGflow(bool value); - void setGfhigh(bool value); - void setHRgraph(bool value); - void setTankBar(bool value); - void setPercentageGraph(bool value); - void setRulerGraph(bool value); - void setShowCCRSetpoint(bool value); - void setShowCCRSensors(bool value); - void setZoomedPlot(bool value); - void setShowSac(bool value); - void setGfLowAtMaxDepth(bool value); - void setDisplayUnusedTanks(bool value); - void setShowAverageDepth(bool value); - void setShowPicturesInProfile(bool value); - -signals: - void modpO2Changed(double value); - void eadChanged(bool value); - void modChanged(bool value); - void dcceilingChanged(bool value); - void redceilingChanged(bool value); - void calcceilingChanged(bool value); - void calcceiling3mChanged(bool value); - void calcalltissuesChanged(bool value); - void calcndlttsChanged(bool value); - void gflowChanged(bool value); - void gfhighChanged(bool value); - void hrgraphChanged(bool value); - void tankBarChanged(bool value); - void percentageGraphChanged(bool value); - void rulerGraphChanged(bool value); - void showCCRSetpointChanged(bool value); - void showCCRSensorsChanged(bool value); - void zoomedPlotChanged(bool value); - void showSacChanged(bool value); - void gfLowAtMaxDepthChanged(bool value); - void displayUnusedTanksChanged(bool value); - void showAverageDepthChanged(bool value); - void showPicturesInProfileChanged(bool value); -}; - -/* Control the state of the Facebook preferences */ -class FacebookSettings : public QObject { - Q_OBJECT - Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged) - Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged) - Q_PROPERTY(QString albumId READ albumId WRITE setAlbumId NOTIFY albumIdChanged) - -public: - FacebookSettings(QObject *parent); - QString accessToken() const; - QString userId() const; - QString albumId() const; - -public slots: - void setAccessToken (const QString& value); - void setUserId(const QString& value); - void setAlbumId(const QString& value); - -signals: - void accessTokenChanged(const QString& value); - void userIdChanged(const QString& value); - void albumIdChanged(const QString& value); -private: - QString group; - QString subgroup; -}; - -/* Control the state of the Geocoding preferences */ -class GeocodingPreferences : public QObject { - Q_OBJECT - Q_PROPERTY(bool enable_geocoding READ enableGeocoding WRITE setEnableGeocoding NOTIFY enableGeocodingChanged) - Q_PROPERTY(bool parse_dive_without_gps READ parseDiveWithoutGps WRITE setParseDiveWithoutGps NOTIFY parseDiveWithoutGpsChanged) - Q_PROPERTY(bool tag_existing_dives READ tagExistingDives WRITE setTagExistingDives NOTIFY tagExistingDivesChanged) - Q_PROPERTY(taxonomy_category first_category READ firstTaxonomyCategory WRITE setFirstTaxonomyCategory NOTIFY firstTaxonomyCategoryChanged) - Q_PROPERTY(taxonomy_category second_category READ secondTaxonomyCategory WRITE setSecondTaxonomyCategory NOTIFY secondTaxonomyCategoryChanged) - Q_PROPERTY(taxonomy_category third_category READ thirdTaxonomyCategory WRITE setThirdTaxonomyCategory NOTIFY thirdTaxonomyCategoryChanged) -public: - GeocodingPreferences(QObject *parent); - bool enableGeocoding() const; - bool parseDiveWithoutGps() const; - bool tagExistingDives() const; - taxonomy_category firstTaxonomyCategory() const; - taxonomy_category secondTaxonomyCategory() const; - taxonomy_category thirdTaxonomyCategory() const; - -public slots: - void setEnableGeocoding(bool value); - void setParseDiveWithoutGps(bool value); - void setTagExistingDives(bool value); - void setFirstTaxonomyCategory(taxonomy_category value); - void setSecondTaxonomyCategory(taxonomy_category value); - void setThirdTaxonomyCategory(taxonomy_category value); - -signals: - void enableGeocodingChanged(bool value); - void parseDiveWithoutGpsChanged(bool value); - void tagExistingDivesChanged(bool value); - void firstTaxonomyCategoryChanged(taxonomy_category value); - void secondTaxonomyCategoryChanged(taxonomy_category value); - void thirdTaxonomyCategoryChanged(taxonomy_category value); -private: - QString group; -}; - -class ProxySettings : public QObject { - Q_OBJECT - Q_PROPERTY(int type READ type WRITE setType NOTIFY typeChanged) - Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged) - Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged) - Q_PROPERTY(short auth READ auth WRITE setAuth NOTIFY authChanged) - Q_PROPERTY(QString user READ user WRITE setUser NOTIFY userChanged) - Q_PROPERTY(QString pass READ pass WRITE setPass NOTIFY passChanged) - -public: - ProxySettings(QObject *parent); - int type() const; - QString host() const; - int port() const; - short auth() const; - QString user() const; - QString pass() const; - -public slots: - void setType(int value); - void setHost(const QString& value); - void setPort(int value); - void setAuth(short value); - void setUser(const QString& value); - void setPass(const QString& value); - -signals: - void typeChanged(int value); - void hostChanged(const QString& value); - void portChanged(int value); - void authChanged(short value); - void userChanged(const QString& value); - void passChanged(const QString& value); -private: - QString group; -}; - -class CloudStorageSettings : public QObject { - Q_OBJECT - Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) - Q_PROPERTY(QString newpassword READ newPassword WRITE setNewPassword NOTIFY newPasswordChanged) - Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged) - Q_PROPERTY(QString email_encoded READ emailEncoded WRITE setEmailEncoded NOTIFY emailEncodedChanged) - Q_PROPERTY(QString userid READ userId WRITE setUserId NOTIFY userIdChanged) - Q_PROPERTY(QString base_url READ baseUrl WRITE setBaseUrl NOTIFY baseUrlChanged) - Q_PROPERTY(QString git_url READ gitUrl WRITE setGitUrl NOTIFY gitUrlChanged) - Q_PROPERTY(bool git_local_only READ gitLocalOnly WRITE setGitLocalOnly NOTIFY gitLocalOnlyChanged) - Q_PROPERTY(bool save_password_local READ savePasswordLocal WRITE setSavePasswordLocal NOTIFY savePasswordLocalChanged) - Q_PROPERTY(short verification_status READ verificationStatus WRITE setVerificationStatus NOTIFY verificationStatusChanged) - Q_PROPERTY(bool background_sync READ backgroundSync WRITE setBackgroundSync NOTIFY backgroundSyncChanged) -public: - CloudStorageSettings(QObject *parent); - QString password() const; - QString newPassword() const; - QString email() const; - QString emailEncoded() const; - QString userId() const; - QString baseUrl() const; - QString gitUrl() const; - bool savePasswordLocal() const; - short verificationStatus() const; - bool backgroundSync() const; - bool gitLocalOnly() const; - -public slots: - void setPassword(const QString& value); - void setNewPassword(const QString& value); - void setEmail(const QString& value); - void setEmailEncoded(const QString& value); - void setUserId(const QString& value); - void setBaseUrl(const QString& value); - void setGitUrl(const QString& value); - void setSavePasswordLocal(bool value); - void setVerificationStatus(short value); - void setBackgroundSync(bool value); - void setGitLocalOnly(bool value); - -signals: - void passwordChanged(const QString& value); - void newPasswordChanged(const QString& value); - void emailChanged(const QString& value); - void emailEncodedChanged(const QString& value); - void userIdChanged(const QString& value); - void baseUrlChanged(const QString& value); - void gitUrlChanged(const QString& value); - void savePasswordLocalChanged(bool value); - void verificationStatusChanged(short value); - void backgroundSyncChanged(bool value); - void gitLocalOnlyChanged(bool value); -private: - QString group; -}; - -class DivePlannerSettings : public QObject { - Q_OBJECT - Q_PROPERTY(bool last_stop READ lastStop WRITE setLastStop NOTIFY lastStopChanged) - Q_PROPERTY(bool verbatim_plan READ verbatimPlan WRITE setVerbatimPlan NOTIFY verbatimPlanChanged) - Q_PROPERTY(bool display_runtime READ displayRuntime WRITE setDisplayRuntime NOTIFY displayRuntimeChanged) - Q_PROPERTY(bool display_duration READ displayDuration WRITE setDisplayDuration NOTIFY displayDurationChanged) - Q_PROPERTY(bool display_transitions READ displayTransitions WRITE setDisplayTransitions NOTIFY displayTransitionsChanged) - Q_PROPERTY(bool doo2breaks READ doo2breaks WRITE setDoo2breaks NOTIFY doo2breaksChanged) - Q_PROPERTY(bool drop_stone_mode READ dropStoneMode WRITE setDropStoneMode NOTIFY dropStoneModeChanged) - Q_PROPERTY(bool safetystop READ safetyStop WRITE setSafetyStop NOTIFY safetyStopChanged) - Q_PROPERTY(bool switch_at_req_stop READ switchAtRequiredStop WRITE setSwitchAtRequiredStop NOTIFY switchAtRequiredStopChanged) - Q_PROPERTY(int ascrate75 READ ascrate75 WRITE setAscrate75 NOTIFY ascrate75Changed) - Q_PROPERTY(int ascrate50 READ ascrate50 WRITE setAscrate50 NOTIFY ascrate50Changed) - Q_PROPERTY(int ascratestops READ ascratestops WRITE setAscratestops NOTIFY ascratestopsChanged) - Q_PROPERTY(int ascratelast6m READ ascratelast6m WRITE setAscratelast6m NOTIFY ascratelast6mChanged) - Q_PROPERTY(int descrate READ descrate WRITE setDescrate NOTIFY descrateChanged) - Q_PROPERTY(int bottompo2 READ bottompo2 WRITE setBottompo2 NOTIFY bottompo2Changed) - Q_PROPERTY(int decopo2 READ decopo2 WRITE setDecopo2 NOTIFY decopo2Changed) - Q_PROPERTY(int reserve_gas READ reserveGas WRITE setReserveGas NOTIFY reserveGasChanged) - Q_PROPERTY(int min_switch_duration READ minSwitchDuration WRITE setMinSwitchDuration NOTIFY minSwitchDurationChanged) - Q_PROPERTY(int bottomsac READ bottomSac WRITE setBottomSac NOTIFY bottomSacChanged) - Q_PROPERTY(int decosac READ decoSac WRITE setSecoSac NOTIFY decoSacChanged) - Q_PROPERTY(short conservatism_level READ conservatismLevel WRITE setConservatismLevel NOTIFY conservatismLevelChanged) - Q_PROPERTY(deco_mode decoMode READ decoMode WRITE setDecoMode NOTIFY decoModeChanged) - -public: - DivePlannerSettings(QObject *parent = 0); - bool lastStop() const; - bool verbatimPlan() const; - bool displayRuntime() const; - bool displayDuration() const; - bool displayTransitions() const; - bool doo2breaks() const; - bool dropStoneMode() const; - bool safetyStop() const; - bool switchAtRequiredStop() const; - int ascrate75() const; - int ascrate50() const; - int ascratestops() const; - int ascratelast6m() const; - int descrate() const; - int bottompo2() const; - int decopo2() const; - int reserveGas() const; - int minSwitchDuration() const; - int bottomSac() const; - int decoSac() const; - short conservatismLevel() const; - deco_mode decoMode() const; - -public slots: - void setLastStop(bool value); - void setVerbatimPlan(bool value); - void setDisplayRuntime(bool value); - void setDisplayDuration(bool value); - void setDisplayTransitions(bool value); - void setDoo2breaks(bool value); - void setDropStoneMode(bool value); - void setSafetyStop(bool value); - void setSwitchAtRequiredStop(bool value); - void setAscrate75(int value); - void setAscrate50(int value); - void setAscratestops(int value); - void setAscratelast6m(int value); - void setDescrate(int value); - void setBottompo2(int value); - void setDecopo2(int value); - void setReserveGas(int value); - void setMinSwitchDuration(int value); - void setBottomSac(int value); - void setSecoSac(int value); - void setConservatismLevel(int value); - void setDecoMode(deco_mode value); - -signals: - void lastStopChanged(bool value); - void verbatimPlanChanged(bool value); - void displayRuntimeChanged(bool value); - void displayDurationChanged(bool value); - void displayTransitionsChanged(bool value); - void doo2breaksChanged(bool value); - void dropStoneModeChanged(bool value); - void safetyStopChanged(bool value); - void switchAtRequiredStopChanged(bool value); - void ascrate75Changed(int value); - void ascrate50Changed(int value); - void ascratestopsChanged(int value); - void ascratelast6mChanged(int value); - void descrateChanged(int value); - void bottompo2Changed(int value); - void decopo2Changed(int value); - void reserveGasChanged(int value); - void minSwitchDurationChanged(int value); - void bottomSacChanged(int value); - void decoSacChanged(int value); - void conservatismLevelChanged(int value); - void decoModeChanged(deco_mode value); - -private: - QString group; -}; - -class UnitsSettings : public QObject { - Q_OBJECT - Q_PROPERTY(int length READ length WRITE setLength NOTIFY lengthChanged) - Q_PROPERTY(int pressure READ pressure WRITE setPressure NOTIFY pressureChanged) - Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged) - Q_PROPERTY(int temperature READ temperature WRITE setTemperature NOTIFY temperatureChanged) - Q_PROPERTY(int weight READ weight WRITE setWeight NOTIFY weightChanged) - Q_PROPERTY(QString unit_system READ unitSystem WRITE setUnitSystem NOTIFY unitSystemChanged) - Q_PROPERTY(bool coordinates_traditional READ coordinatesTraditional WRITE setCoordinatesTraditional NOTIFY coordinatesTraditionalChanged) - Q_PROPERTY(int vertical_speed_time READ verticalSpeedTime WRITE setVerticalSpeedTime NOTIFY verticalSpeedTimeChanged) - -public: - UnitsSettings(QObject *parent = 0); - int length() const; - int pressure() const; - int volume() const; - int temperature() const; - int weight() const; - int verticalSpeedTime() const; - QString unitSystem() const; - bool coordinatesTraditional() const; - -public slots: - void setLength(int value); - void setPressure(int value); - void setVolume(int value); - void setTemperature(int value); - void setWeight(int value); - void setVerticalSpeedTime(int value); - void setUnitSystem(const QString& value); - void setCoordinatesTraditional(bool value); - -signals: - void lengthChanged(int value); - void pressureChanged(int value); - void volumeChanged(int value); - void temperatureChanged(int value); - void weightChanged(int value); - void verticalSpeedTimeChanged(int value); - void unitSystemChanged(const QString& value); - void coordinatesTraditionalChanged(bool value); -private: - QString group; -}; - -class GeneralSettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(QString default_filename READ defaultFilename WRITE setDefaultFilename NOTIFY defaultFilenameChanged) - Q_PROPERTY(QString default_cylinder READ defaultCylinder WRITE setDefaultCylinder NOTIFY defaultCylinderChanged) - Q_PROPERTY(short default_file_behavior READ defaultFileBehavior WRITE setDefaultFileBehavior NOTIFY defaultFileBehaviorChanged) - Q_PROPERTY(bool use_default_file READ useDefaultFile WRITE setUseDefaultFile NOTIFY useDefaultFileChanged) - Q_PROPERTY(int defaultsetpoint READ defaultSetPoint WRITE setDefaultSetPoint NOTIFY defaultSetPointChanged) - Q_PROPERTY(int o2consumption READ o2Consumption WRITE setO2Consumption NOTIFY o2ConsumptionChanged) - Q_PROPERTY(int pscr_ratio READ pscrRatio WRITE setPscrRatio NOTIFY pscrRatioChanged) - -public: - GeneralSettingsObjectWrapper(QObject *parent); - QString defaultFilename() const; - QString defaultCylinder() const; - short defaultFileBehavior() const; - bool useDefaultFile() const; - int defaultSetPoint() const; - int o2Consumption() const; - int pscrRatio() const; - -public slots: - void setDefaultFilename (const QString& value); - void setDefaultCylinder (const QString& value); - void setDefaultFileBehavior (short value); - void setUseDefaultFile (bool value); - void setDefaultSetPoint (int value); - void setO2Consumption (int value); - void setPscrRatio (int value); - -signals: - void defaultFilenameChanged(const QString& value); - void defaultCylinderChanged(const QString& value); - void defaultFileBehaviorChanged(short value); - void useDefaultFileChanged(bool value); - void defaultSetPointChanged(int value); - void o2ConsumptionChanged(int value); - void pscrRatioChanged(int value); -private: - QString group; -}; - -class DisplaySettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(QString divelist_font READ divelistFont WRITE setDivelistFont NOTIFY divelistFontChanged) - Q_PROPERTY(double font_size READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) - Q_PROPERTY(short display_invalid_dives READ displayInvalidDives WRITE setDisplayInvalidDives NOTIFY displayInvalidDivesChanged) -public: - DisplaySettingsObjectWrapper(QObject *parent); - QString divelistFont() const; - double fontSize() const; - short displayInvalidDives() const; -public slots: - void setDivelistFont(const QString& value); - void setFontSize(double value); - void setDisplayInvalidDives(short value); -signals: - void divelistFontChanged(const QString& value); - void fontSizeChanged(double value); - void displayInvalidDivesChanged(short value); -private: - QString group; -}; - -class LanguageSettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged) - Q_PROPERTY(QString time_format READ timeFormat WRITE setTimeFormat NOTIFY timeFormatChanged) - Q_PROPERTY(QString date_format READ dateFormat WRITE setDateFormat NOTIFY dateFormatChanged) - Q_PROPERTY(QString date_format_short READ dateFormatShort WRITE setDateFormatShort NOTIFY dateFormatShortChanged) - Q_PROPERTY(bool time_format_override READ timeFormatOverride WRITE setTimeFormatOverride NOTIFY timeFormatOverrideChanged) - Q_PROPERTY(bool date_format_override READ dateFormatOverride WRITE setDateFormatOverride NOTIFY dateFormatOverrideChanged) - Q_PROPERTY(bool use_system_language READ useSystemLanguage WRITE setUseSystemLanguage NOTIFY useSystemLanguageChanged) - -public: - LanguageSettingsObjectWrapper(QObject *parent); - QString language() const; - QString timeFormat() const; - QString dateFormat() const; - QString dateFormatShort() const; - bool timeFormatOverride() const; - bool dateFormatOverride() const; - bool useSystemLanguage() const; - -public slots: - void setLanguage (const QString& value); - void setTimeFormat (const QString& value); - void setDateFormat (const QString& value); - void setDateFormatShort (const QString& value); - void setTimeFormatOverride (bool value); - void setDateFormatOverride (bool value); - void setUseSystemLanguage (bool value); -signals: - void languageChanged(const QString& value); - void timeFormatChanged(const QString& value); - void dateFormatChanged(const QString& value); - void dateFormatShortChanged(const QString& value); - void timeFormatOverrideChanged(bool value); - void dateFormatOverrideChanged(bool value); - void useSystemLanguageChanged(bool value); - -private: - QString group; -}; - -class AnimationsSettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(int animation_speed READ animationSpeed WRITE setAnimationSpeed NOTIFY animationSpeedChanged) -public: - AnimationsSettingsObjectWrapper(QObject *parent); - int animationSpeed() const; - -public slots: - void setAnimationSpeed(int value); - -signals: - void animationSpeedChanged(int value); - -private: - QString group; -}; - -class LocationServiceSettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(int time_threshold READ timeThreshold WRITE setTimeThreshold NOTIFY timeThresholdChanged) - Q_PROPERTY(int distance_threshold READ distanceThreshold WRITE setDistanceThreshold NOTIFY distanceThresholdChanged) -public: - LocationServiceSettingsObjectWrapper(QObject *parent); - int timeThreshold() const; - int distanceThreshold() const; -public slots: - void setTimeThreshold(int value); - void setDistanceThreshold(int value); -signals: - void timeThresholdChanged(int value); - void distanceThresholdChanged(int value); -private: - QString group; -}; - -class SettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(short save_userid_local READ saveUserIdLocal WRITE setSaveUserIdLocal NOTIFY saveUserIdLocalChanged) - - Q_PROPERTY(TechnicalDetailsSettings* techical_details MEMBER techDetails CONSTANT) - Q_PROPERTY(PartialPressureGasSettings* pp_gas MEMBER pp_gas CONSTANT) - Q_PROPERTY(FacebookSettings* facebook MEMBER facebook CONSTANT) - Q_PROPERTY(GeocodingPreferences* geocoding MEMBER geocoding CONSTANT) - Q_PROPERTY(ProxySettings* proxy MEMBER proxy CONSTANT) - Q_PROPERTY(CloudStorageSettings* cloud_storage MEMBER cloud_storage CONSTANT) - Q_PROPERTY(DivePlannerSettings* planner MEMBER planner_settings CONSTANT) - Q_PROPERTY(UnitsSettings* units MEMBER unit_settings CONSTANT) - - Q_PROPERTY(GeneralSettingsObjectWrapper* general MEMBER general_settings CONSTANT) - Q_PROPERTY(DisplaySettingsObjectWrapper* display MEMBER display_settings CONSTANT) - Q_PROPERTY(LanguageSettingsObjectWrapper* language MEMBER language_settings CONSTANT) - Q_PROPERTY(AnimationsSettingsObjectWrapper* animation MEMBER animation_settings CONSTANT) - Q_PROPERTY(LocationServiceSettingsObjectWrapper* Location MEMBER location_settings CONSTANT) -public: - static SettingsObjectWrapper *instance(); - short saveUserIdLocal() const; - - TechnicalDetailsSettings *techDetails; - PartialPressureGasSettings *pp_gas; - FacebookSettings *facebook; - GeocodingPreferences *geocoding; - ProxySettings *proxy; - CloudStorageSettings *cloud_storage; - DivePlannerSettings *planner_settings; - UnitsSettings *unit_settings; - GeneralSettingsObjectWrapper *general_settings; - DisplaySettingsObjectWrapper *display_settings; - LanguageSettingsObjectWrapper *language_settings; - AnimationsSettingsObjectWrapper *animation_settings; - LocationServiceSettingsObjectWrapper *location_settings; - -public slots: - void setSaveUserIdLocal(short value); -private: - SettingsObjectWrapper(QObject *parent = NULL); -signals: - void saveUserIdLocalChanged(short value); -}; - -#endif diff --git a/subsurface-core/subsurfacestartup.c b/subsurface-core/subsurfacestartup.c deleted file mode 100644 index 6e0dede1c..000000000 --- a/subsurface-core/subsurfacestartup.c +++ /dev/null @@ -1,310 +0,0 @@ -#include "subsurfacestartup.h" -#include "version.h" -#include <stdbool.h> -#include <string.h> -#include "gettext.h" -#include "qthelperfromc.h" -#include "git-access.h" -#include "libdivecomputer/version.h" - -struct preferences prefs, informational_prefs; -struct preferences default_prefs = { - .cloud_base_url = "https://cloud.subsurface-divelog.org/", - .units = SI_UNITS, - .unit_system = METRIC, - .coordinates_traditional = true, - .pp_graphs = { - .po2 = false, - .pn2 = false, - .phe = false, - .po2_threshold = 1.6, - .pn2_threshold = 4.0, - .phe_threshold = 13.0, - }, - .mod = false, - .modpO2 = 1.6, - .ead = false, - .hrgraph = false, - .percentagegraph = false, - .dcceiling = true, - .redceiling = false, - .calcceiling = false, - .calcceiling3m = false, - .calcndltts = false, - .gflow = 30, - .gfhigh = 75, - .animation_speed = 500, - .gf_low_at_maxdepth = false, - .show_ccr_setpoint = false, - .show_ccr_sensors = false, - .font_size = -1, - .display_invalid_dives = false, - .show_sac = false, - .display_unused_tanks = false, - .show_average_depth = true, - .ascrate75 = 9000 / 60, - .ascrate50 = 6000 / 60, - .ascratestops = 6000 / 60, - .ascratelast6m = 1000 / 60, - .descrate = 18000 / 60, - .bottompo2 = 1400, - .decopo2 = 1600, - .doo2breaks = false, - .drop_stone_mode = false, - .switch_at_req_stop = false, - .min_switch_duration = 60, - .last_stop = false, - .verbatim_plan = false, - .display_runtime = true, - .display_duration = true, - .display_transitions = true, - .safetystop = true, - .bottomsac = 20000, - .decosac = 17000, - .reserve_gas=40000, - .o2consumption = 720, - .pscr_ratio = 100, - .show_pictures_in_profile = true, - .tankbar = false, - .facebook = { - .user_id = NULL, - .album_id = NULL, - .access_token = NULL - }, - .defaultsetpoint = 1100, - .cloud_background_sync = true, - .geocoding = { - .enable_geocoding = true, - .parse_dive_without_gps = false, - .tag_existing_dives = false, - .category = { 0 } - }, - .deco_mode = BUEHLMANN, - .conservatism_level = 3, - .distance_threshold = 1000, - .time_threshold = 600 -}; - -int run_survey; - -struct units *get_units() -{ - return &prefs.units; -} - -/* random helper functions, used here or elsewhere */ -static int sortfn(const void *_a, const void *_b) -{ - const struct dive *a = (const struct dive *)*(void **)_a; - const struct dive *b = (const struct dive *)*(void **)_b; - - if (a->when < b->when) - return -1; - if (a->when > b->when) - return 1; - return 0; -} - -void sort_table(struct dive_table *table) -{ - qsort(table->dives, table->nr, sizeof(struct dive *), sortfn); -} - -const char *weekday(int wday) -{ - static const char wday_array[7][7] = { - /*++GETTEXT: these are three letter days - we allow up to six code bytes */ - QT_TRANSLATE_NOOP("gettextFromC", "Sun"), QT_TRANSLATE_NOOP("gettextFromC", "Mon"), QT_TRANSLATE_NOOP("gettextFromC", "Tue"), QT_TRANSLATE_NOOP("gettextFromC", "Wed"), QT_TRANSLATE_NOOP("gettextFromC", "Thu"), QT_TRANSLATE_NOOP("gettextFromC", "Fri"), QT_TRANSLATE_NOOP("gettextFromC", "Sat") - }; - return translate("gettextFromC", wday_array[wday]); -} - -const char *monthname(int mon) -{ - static const char month_array[12][7] = { - /*++GETTEXT: these are three letter months - we allow up to six code bytes*/ - QT_TRANSLATE_NOOP("gettextFromC", "Jan"), QT_TRANSLATE_NOOP("gettextFromC", "Feb"), QT_TRANSLATE_NOOP("gettextFromC", "Mar"), QT_TRANSLATE_NOOP("gettextFromC", "Apr"), QT_TRANSLATE_NOOP("gettextFromC", "May"), QT_TRANSLATE_NOOP("gettextFromC", "Jun"), - QT_TRANSLATE_NOOP("gettextFromC", "Jul"), QT_TRANSLATE_NOOP("gettextFromC", "Aug"), QT_TRANSLATE_NOOP("gettextFromC", "Sep"), QT_TRANSLATE_NOOP("gettextFromC", "Oct"), QT_TRANSLATE_NOOP("gettextFromC", "Nov"), QT_TRANSLATE_NOOP("gettextFromC", "Dec"), - }; - return translate("gettextFromC", month_array[mon]); -} - -/* - * track whether we switched to importing dives - */ -bool imported = false; - -static void print_version() -{ - printf("Subsurface v%s, ", subsurface_git_version()); - printf("built with libdivecomputer v%s\n", dc_version(NULL)); -} - -void print_files() -{ - const char *branch = 0; - const char *remote = 0; - const char *filename, *local_git; - - filename = cloud_url(); - - is_git_repository(filename, &branch, &remote, true); - printf("\nFile locations:\n\n"); - if (branch && remote) { - local_git = get_local_dir(remote, branch); - printf("Local git storage: %s\n", local_git); - } else { - printf("Unable to get local git directory\n"); - } - char *tmp = cloud_url(); - printf("Cloud URL: %s\n", tmp); - free(tmp); - tmp = hashfile_name_string(); - printf("Image hashes: %s\n", tmp); - free(tmp); - tmp = picturedir_string(); - printf("Local picture directory: %s\n\n", tmp); - free(tmp); -} - -static void print_help() -{ - print_version(); - printf("\nUsage: subsurface [options] [logfile ...] [--import logfile ...]"); - printf("\n\noptions include:"); - printf("\n --help|-h This help text"); - printf("\n --import logfile ... Logs before this option is treated as base, everything after is imported"); - printf("\n --verbose|-v Verbose debug (repeat to increase verbosity)"); - printf("\n --version Prints current version"); - printf("\n --survey Offer to submit a user survey"); - printf("\n --win32console Create a dedicated console if needed (Windows only). Add option before everything else\n\n"); -} - -void parse_argument(const char *arg) -{ - const char *p = arg + 1; - - do { - switch (*p) { - case 'h': - print_help(); - exit(0); - case 'v': - verbose++; - continue; - case 'q': - quit++; - continue; - case '-': - /* long options with -- */ - if (strcmp(arg, "--help") == 0) { - print_help(); - exit(0); - } - if (strcmp(arg, "--import") == 0) { - imported = true; /* mark the dives so far as the base, * everything after is imported */ - return; - } - if (strcmp(arg, "--verbose") == 0) { - verbose++; - return; - } - if (strcmp(arg, "--version") == 0) { - print_version(); - exit(0); - } - if (strcmp(arg, "--survey") == 0) { - run_survey = true; - return; - } - if (strcmp(arg, "--allow_run_as_root") == 0) { - ++force_root; - return; - } - if (strcmp(arg, "--win32console") == 0) - return; - /* fallthrough */ - case 'p': - /* ignore process serial number argument when run as native macosx app */ - if (strncmp(arg, "-psQT_TR_NOOP(", 5) == 0) { - return; - } - /* fallthrough */ - default: - fprintf(stderr, "Bad argument '%s'\n", arg); - exit(1); - } - } while (*++p); -} - -/* - * Under a POSIX setup, the locale string should have a format - * like [language[_territory][.codeset][@modifier]]. - * - * So search for the underscore, and see if the "territory" is - * US, and turn on imperial units by default. - * - * I guess Burma and Liberia should trigger this too. I'm too - * lazy to look up the territory names, though. - */ -void setup_system_prefs(void) -{ - const char *env; - - subsurface_OS_pref_setup(); - default_prefs.divelist_font = strdup(system_divelist_default_font); - default_prefs.font_size = system_divelist_default_font_size; - default_prefs.default_filename = system_default_filename(); - - env = getenv("LC_MEASUREMENT"); - if (!env) - env = getenv("LC_ALL"); - if (!env) - env = getenv("LANG"); - if (!env) - return; - env = strchr(env, '_'); - if (!env) - return; - env++; - if (strncmp(env, "US", 2)) - return; - - default_prefs.units = IMPERIAL_units; -} - -/* copy a preferences block, including making copies of all included strings */ -void copy_prefs(struct preferences *src, struct preferences *dest) -{ - *dest = *src; - dest->divelist_font = copy_string(src->divelist_font); - dest->default_filename = copy_string(src->default_filename); - dest->default_cylinder = copy_string(src->default_cylinder); - dest->cloud_base_url = copy_string(src->cloud_base_url); - dest->cloud_git_url = copy_string(src->cloud_git_url); - dest->userid = copy_string(src->userid); - dest->proxy_host = copy_string(src->proxy_host); - dest->proxy_user = copy_string(src->proxy_user); - dest->proxy_pass = copy_string(src->proxy_pass); - dest->time_format = copy_string(src->time_format); - dest->date_format = copy_string(src->date_format); - dest->date_format_short = copy_string(src->date_format_short); - dest->cloud_storage_password = copy_string(src->cloud_storage_password); - dest->cloud_storage_newpassword = copy_string(src->cloud_storage_newpassword); - dest->cloud_storage_email = copy_string(src->cloud_storage_email); - dest->cloud_storage_email_encoded = copy_string(src->cloud_storage_email_encoded); - dest->facebook.access_token = copy_string(src->facebook.access_token); - dest->facebook.user_id = copy_string(src->facebook.user_id); - dest->facebook.album_id = copy_string(src->facebook.album_id); -} - -/* - * Free strduped prefs before exit. - * - * These are not real leaks but they plug the holes found by eg. - * valgrind so you can find the real leaks. - */ -void free_prefs(void) -{ - // nop -} diff --git a/subsurface-core/subsurfacestartup.h b/subsurface-core/subsurfacestartup.h deleted file mode 100644 index 3ccc24aa4..000000000 --- a/subsurface-core/subsurfacestartup.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef SUBSURFACESTARTUP_H -#define SUBSURFACESTARTUP_H - -#include "dive.h" -#include "divelist.h" -#include "libdivecomputer.h" - -#ifdef __cplusplus -extern "C" { -#else -#include <stdbool.h> -#endif - -extern bool imported; - -void setup_system_prefs(void); -void parse_argument(const char *arg); -void free_prefs(void); -void copy_prefs(struct preferences *src, struct preferences *dest); -void print_files(void); - -#ifdef __cplusplus -} -#endif - -#endif // SUBSURFACESTARTUP_H diff --git a/subsurface-core/subsurfacesysinfo.cpp b/subsurface-core/subsurfacesysinfo.cpp deleted file mode 100644 index a7173b169..000000000 --- a/subsurface-core/subsurfacesysinfo.cpp +++ /dev/null @@ -1,620 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Copyright (C) 2014 Intel Corporation -** Contact: http://www.qt-project.org/legal -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "subsurfacesysinfo.h" -#include <QString> - -#ifdef Q_OS_UNIX -#include <sys/utsname.h> -#endif - -#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) - -#ifndef QStringLiteral -# define QStringLiteral QString::fromUtf8 -#endif - -#ifdef Q_OS_UNIX -#include <qplatformdefs.h> -#endif - -#ifdef __APPLE__ -#include <CoreServices/CoreServices.h> -#endif - -// --- this is a copy of Qt 5.4's src/corelib/global/archdetect.cpp --- - -// main part: processor type -#if defined(Q_PROCESSOR_ALPHA) -# define ARCH_PROCESSOR "alpha" -#elif defined(Q_PROCESSOR_ARM) -# define ARCH_PROCESSOR "arm" -#elif defined(Q_PROCESSOR_AVR32) -# define ARCH_PROCESSOR "avr32" -#elif defined(Q_PROCESSOR_BLACKFIN) -# define ARCH_PROCESSOR "bfin" -#elif defined(Q_PROCESSOR_X86_32) -# define ARCH_PROCESSOR "i386" -#elif defined(Q_PROCESSOR_X86_64) -# define ARCH_PROCESSOR "x86_64" -#elif defined(Q_PROCESSOR_IA64) -# define ARCH_PROCESSOR "ia64" -#elif defined(Q_PROCESSOR_MIPS) -# define ARCH_PROCESSOR "mips" -#elif defined(Q_PROCESSOR_POWER) -# define ARCH_PROCESSOR "power" -#elif defined(Q_PROCESSOR_S390) -# define ARCH_PROCESSOR "s390" -#elif defined(Q_PROCESSOR_SH) -# define ARCH_PROCESSOR "sh" -#elif defined(Q_PROCESSOR_SPARC) -# define ARCH_PROCESSOR "sparc" -#else -# define ARCH_PROCESSOR "unknown" -#endif - -// endinanness -#if defined(Q_LITTLE_ENDIAN) -# define ARCH_ENDIANNESS "little_endian" -#elif defined(Q_BIG_ENDIAN) -# define ARCH_ENDIANNESS "big_endian" -#endif - -// pointer type -#if defined(Q_OS_WIN64) || (defined(Q_OS_WINRT) && defined(_M_X64)) -# define ARCH_POINTER "llp64" -#elif defined(__LP64__) || QT_POINTER_SIZE - 0 == 8 -# define ARCH_POINTER "lp64" -#else -# define ARCH_POINTER "ilp32" -#endif - -// secondary: ABI string (includes the dash) -#if defined(__ARM_EABI__) || defined(__mips_eabi) -# define ARCH_ABI1 "-eabi" -#elif defined(_MIPS_SIM) -# if _MIPS_SIM == _ABIO32 -# define ARCH_ABI1 "-o32" -# elif _MIPS_SIM == _ABIN32 -# define ARCH_ABI1 "-n32" -# elif _MIPS_SIM == _ABI64 -# define ARCH_ABI1 "-n64" -# elif _MIPS_SIM == _ABIO64 -# define ARCH_ABI1 "-o64" -# endif -#else -# define ARCH_ABI1 "" -#endif -#if defined(__ARM_PCS_VFP) || defined(__mips_hard_float) -# define ARCH_ABI2 "-hardfloat" -#else -# define ARCH_ABI2 "" -#endif - -#define ARCH_ABI ARCH_ABI1 ARCH_ABI2 - -#define ARCH_FULL ARCH_PROCESSOR "-" ARCH_ENDIANNESS "-" ARCH_POINTER ARCH_ABI - -// --- end of archdetect.cpp --- -// copied from Qt 5.4.1's src/corelib/global/qglobal.cpp - -#if defined(Q_OS_WIN) || defined(Q_OS_CYGWIN) || defined(Q_OS_WINCE) || defined(Q_OS_WINRT) - -QT_BEGIN_INCLUDE_NAMESPACE -#include "qt_windows.h" -QT_END_INCLUDE_NAMESPACE - -#ifndef Q_OS_WINRT -# ifndef Q_OS_WINCE -// Fallback for determining Windows versions >= 8 by looping using the -// version check macros. Note that it will return build number=0 to avoid -// inefficient looping. -static inline void determineWinOsVersionFallbackPost8(OSVERSIONINFO *result) -{ - result->dwBuildNumber = 0; - DWORDLONG conditionMask = 0; - VER_SET_CONDITION(conditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(conditionMask, VER_PLATFORMID, VER_EQUAL); - OSVERSIONINFOEX checkVersion = { sizeof(OSVERSIONINFOEX), result->dwMajorVersion, 0, - result->dwBuildNumber, result->dwPlatformId, {'\0'}, 0, 0, 0, 0, 0 }; - for ( ; VerifyVersionInfo(&checkVersion, VER_MAJORVERSION | VER_PLATFORMID, conditionMask); ++checkVersion.dwMajorVersion) - result->dwMajorVersion = checkVersion.dwMajorVersion; - conditionMask = 0; - checkVersion.dwMajorVersion = result->dwMajorVersion; - checkVersion.dwMinorVersion = 0; - VER_SET_CONDITION(conditionMask, VER_MAJORVERSION, VER_EQUAL); - VER_SET_CONDITION(conditionMask, VER_MINORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(conditionMask, VER_PLATFORMID, VER_EQUAL); - for ( ; VerifyVersionInfo(&checkVersion, VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID, conditionMask); ++checkVersion.dwMinorVersion) - result->dwMinorVersion = checkVersion.dwMinorVersion; -} - -# endif // !Q_OS_WINCE - -static inline OSVERSIONINFO winOsVersion() -{ - OSVERSIONINFO result = { sizeof(OSVERSIONINFO), 0, 0, 0, 0, {'\0'}}; - // GetVersionEx() has been deprecated in Windows 8.1 and will return - // only Windows 8 from that version on. -# if defined(_MSC_VER) && _MSC_VER >= 1800 -# pragma warning( push ) -# pragma warning( disable : 4996 ) -# endif - GetVersionEx(&result); -# if defined(_MSC_VER) && _MSC_VER >= 1800 -# pragma warning( pop ) -# endif -# ifndef Q_OS_WINCE - if (result.dwMajorVersion == 6 && result.dwMinorVersion == 2) { - determineWinOsVersionFallbackPost8(&result); - } -# endif // !Q_OS_WINCE - return result; -} -#endif // !Q_OS_WINRT - -static const char *winVer_helper() -{ - switch (int(SubsurfaceSysInfo::WindowsVersion)) { - case SubsurfaceSysInfo::WV_NT: - return "NT"; - case SubsurfaceSysInfo::WV_2000: - return "2000"; - case SubsurfaceSysInfo::WV_XP: - return "XP"; - case SubsurfaceSysInfo::WV_2003: - return "2003"; - case SubsurfaceSysInfo::WV_VISTA: - return "Vista"; - case SubsurfaceSysInfo::WV_WINDOWS7: - return "7"; - case SubsurfaceSysInfo::WV_WINDOWS8: - return "8"; - case SubsurfaceSysInfo::WV_WINDOWS8_1: - return "8.1"; - - case SubsurfaceSysInfo::WV_CE: - return "CE"; - case SubsurfaceSysInfo::WV_CENET: - return "CENET"; - case SubsurfaceSysInfo::WV_CE_5: - return "CE5"; - case SubsurfaceSysInfo::WV_CE_6: - return "CE6"; - } - // unknown, future version - return 0; -} -#endif - -#if defined(Q_OS_UNIX) -# if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_FREEBSD) -# define USE_ETC_OS_RELEASE -struct QUnixOSVersion -{ - // from /etc/os-release - QString productType; // $ID - QString productVersion; // $VERSION_ID - QString prettyName; // $PRETTY_NAME -}; - -static QString unquote(const char *begin, const char *end) -{ - if (*begin == '"') { - Q_ASSERT(end[-1] == '"'); - return QString::fromLatin1(begin + 1, end - begin - 2); - } - return QString::fromLatin1(begin, end - begin); -} - -static bool readEtcOsRelease(QUnixOSVersion &v) -{ - // we're avoiding QFile here - int fd = QT_OPEN("/etc/os-release", O_RDONLY); - if (fd == -1) - return false; - - QT_STATBUF sbuf; - if (QT_FSTAT(fd, &sbuf) == -1) { - QT_CLOSE(fd); - return false; - } - - QByteArray buffer(sbuf.st_size, Qt::Uninitialized); - buffer.resize(QT_READ(fd, buffer.data(), sbuf.st_size)); - QT_CLOSE(fd); - - const char *ptr = buffer.constData(); - const char *end = buffer.constEnd(); - const char *eol; - for ( ; ptr != end; ptr = eol + 1) { - static const char idString[] = "ID="; - static const char prettyNameString[] = "PRETTY_NAME="; - static const char versionIdString[] = "VERSION_ID="; - - // find the end of the line after ptr - eol = static_cast<const char *>(memchr(ptr, '\n', end - ptr)); - if (!eol) - eol = end - 1; - - // note: we're doing a binary search here, so comparison - // must always be sorted - int cmp = strncmp(ptr, idString, strlen(idString)); - if (cmp < 0) - continue; - if (cmp == 0) { - ptr += strlen(idString); - v.productType = unquote(ptr, eol); - continue; - } - - cmp = strncmp(ptr, prettyNameString, strlen(prettyNameString)); - if (cmp < 0) - continue; - if (cmp == 0) { - ptr += strlen(prettyNameString); - v.prettyName = unquote(ptr, eol); - continue; - } - - cmp = strncmp(ptr, versionIdString, strlen(versionIdString)); - if (cmp < 0) - continue; - if (cmp == 0) { - ptr += strlen(versionIdString); - v.productVersion = unquote(ptr, eol); - continue; - } - } - - return true; -} -# endif // USE_ETC_OS_RELEASE -#endif // Q_OS_UNIX - -static QString unknownText() -{ - return QStringLiteral("unknown"); -} - -QString SubsurfaceSysInfo::buildCpuArchitecture() -{ - return QStringLiteral(ARCH_PROCESSOR); -} - -QString SubsurfaceSysInfo::currentCpuArchitecture() -{ -#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) - // We don't need to catch all the CPU architectures in this function; - // only those where the host CPU might be different than the build target - // (usually, 64-bit platforms). - SYSTEM_INFO info; - GetNativeSystemInfo(&info); - switch (info.wProcessorArchitecture) { -# ifdef PROCESSOR_ARCHITECTURE_AMD64 - case PROCESSOR_ARCHITECTURE_AMD64: - return QStringLiteral("x86_64"); -# endif -# ifdef PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 - case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: -# endif - case PROCESSOR_ARCHITECTURE_IA64: - return QStringLiteral("ia64"); - } -#elif defined(Q_OS_UNIX) - long ret = -1; - struct utsname u; - -# if defined(Q_OS_SOLARIS) - // We need a special call for Solaris because uname(2) on x86 returns "i86pc" for - // both 32- and 64-bit CPUs. Reference: - // http://docs.oracle.com/cd/E18752_01/html/816-5167/sysinfo-2.html#REFMAN2sysinfo-2 - // http://fxr.watson.org/fxr/source/common/syscall/systeminfo.c?v=OPENSOLARIS - // http://fxr.watson.org/fxr/source/common/conf/param.c?v=OPENSOLARIS;im=10#L530 - if (ret == -1) - ret = sysinfo(SI_ARCHITECTURE_64, u.machine, sizeof u.machine); -# endif - - if (ret == -1) - ret = uname(&u); - - // we could use detectUnixVersion() above, but we only need a field no other function does - if (ret != -1) { - // the use of QT_BUILD_INTERNAL here is simply to ensure all branches build - // as we don't often build on some of the less common platforms -# if defined(Q_PROCESSOR_ARM) || defined(QT_BUILD_INTERNAL) - if (strcmp(u.machine, "aarch64") == 0) - return QStringLiteral("arm64"); - if (strncmp(u.machine, "armv", 4) == 0) - return QStringLiteral("arm"); -# endif -# if defined(Q_PROCESSOR_POWER) || defined(QT_BUILD_INTERNAL) - // harmonize "powerpc" and "ppc" to "power" - if (strncmp(u.machine, "ppc", 3) == 0) - return QLatin1String("power") + QLatin1String(u.machine + 3); - if (strncmp(u.machine, "powerpc", 7) == 0) - return QLatin1String("power") + QLatin1String(u.machine + 7); - if (strcmp(u.machine, "Power Macintosh") == 0) - return QLatin1String("power"); -# endif -# if defined(Q_PROCESSOR_SPARC) || defined(QT_BUILD_INTERNAL) - // Solaris sysinfo(2) (above) uses "sparcv9", but uname -m says "sun4u"; - // Linux says "sparc64" - if (strcmp(u.machine, "sun4u") == 0 || strcmp(u.machine, "sparc64") == 0) - return QStringLiteral("sparcv9"); - if (strcmp(u.machine, "sparc32") == 0) - return QStringLiteral("sparc"); -# endif -# if defined(Q_PROCESSOR_X86) || defined(QT_BUILD_INTERNAL) - // harmonize all "i?86" to "i386" - if (strlen(u.machine) == 4 && u.machine[0] == 'i' - && u.machine[2] == '8' && u.machine[3] == '6') - return QStringLiteral("i386"); - if (strcmp(u.machine, "amd64") == 0) // Solaris - return QStringLiteral("x86_64"); -# endif - return QString::fromLatin1(u.machine); - } -#endif - return buildCpuArchitecture(); -} - - -QString SubsurfaceSysInfo::buildAbi() -{ - return QLatin1String(ARCH_FULL); -} - -QString SubsurfaceSysInfo::kernelType() -{ -#if defined(Q_OS_WINCE) - return QStringLiteral("wince"); -#elif defined(Q_OS_WIN) - return QStringLiteral("winnt"); -#elif defined(Q_OS_UNIX) - struct utsname u; - if (uname(&u) == 0) - return QString::fromLatin1(u.sysname).toLower(); -#endif - return unknownText(); -} - -QString SubsurfaceSysInfo::kernelVersion() -{ -#ifdef Q_OS_WINRT - // TBD - return QString(); -#elif defined(Q_OS_WIN) - const OSVERSIONINFO osver = winOsVersion(); - return QString::number(int(osver.dwMajorVersion)) + QLatin1Char('.') + QString::number(int(osver.dwMinorVersion)) - + QLatin1Char('.') + QString::number(int(osver.dwBuildNumber)); -#else - struct utsname u; - if (uname(&u) == 0) - return QString::fromLatin1(u.release); - return QString(); -#endif -} - -QString SubsurfaceSysInfo::productType() -{ - // similar, but not identical to QFileSelectorPrivate::platformSelectors -#if defined(Q_OS_WINPHONE) - return QStringLiteral("winphone"); -#elif defined(Q_OS_WINRT) - return QStringLiteral("winrt"); -#elif defined(Q_OS_WINCE) - return QStringLiteral("wince"); -#elif defined(Q_OS_WIN) - return QStringLiteral("windows"); - -#elif defined(Q_OS_BLACKBERRY) - return QStringLiteral("blackberry"); -#elif defined(Q_OS_QNX) - return QStringLiteral("qnx"); - -#elif defined(Q_OS_ANDROID) - return QStringLiteral("android"); - -#elif defined(Q_OS_IOS) - return QStringLiteral("ios"); -#elif defined(Q_OS_OSX) - return QStringLiteral("osx"); -#elif defined(Q_OS_DARWIN) - return QStringLiteral("darwin"); - -#elif defined(USE_ETC_OS_RELEASE) // Q_OS_UNIX - QUnixOSVersion unixOsVersion; - readEtcOsRelease(unixOsVersion); - if (!unixOsVersion.productType.isEmpty()) - return unixOsVersion.productType; -#endif - return unknownText(); -} - -QString SubsurfaceSysInfo::productVersion() -{ -#if defined(Q_OS_IOS) - int major = (int(MacintoshVersion) >> 4) & 0xf; - int minor = int(MacintoshVersion) & 0xf; - if (Q_LIKELY(major < 10 && minor < 10)) { - char buf[4] = { char(major + '0'), '.', char(minor + '0'), '\0' }; - return QString::fromLatin1(buf, 3); - } - return QString::number(major) + QLatin1Char('.') + QString::number(minor); -#elif defined(Q_OS_OSX) - int minor = int(MacintoshVersion) - 2; // we're not running on Mac OS 9 - Q_ASSERT(minor < 100); - char buf[] = "10.0\0"; - if (Q_LIKELY(minor < 10)) { - buf[3] += minor; - } else { - buf[3] += minor / 10; - buf[4] = '0' + minor % 10; - } - return QString::fromLatin1(buf); -#elif defined(Q_OS_WIN) - const char *version = winVer_helper(); - if (version) - return QString::fromLatin1(version).toLower(); - // fall through - - // Android and Blackberry should not fall through to the Unix code -#elif defined(Q_OS_ANDROID) - // TBD -#elif defined(Q_OS_BLACKBERRY) - deviceinfo_details_t *deviceInfo; - if (deviceinfo_get_details(&deviceInfo) == BPS_SUCCESS) { - QString bbVersion = QString::fromLatin1(deviceinfo_details_get_device_os_version(deviceInfo)); - deviceinfo_free_details(&deviceInfo); - return bbVersion; - } -#elif defined(USE_ETC_OS_RELEASE) // Q_OS_UNIX - QUnixOSVersion unixOsVersion; - readEtcOsRelease(unixOsVersion); - if (!unixOsVersion.productVersion.isEmpty()) - return unixOsVersion.productVersion; -#endif - - // fallback - return unknownText(); -} - -QString SubsurfaceSysInfo::prettyProductName() -{ -#if defined(Q_OS_IOS) - return QLatin1String("iOS ") + productVersion(); -#elif defined(Q_OS_OSX) - // get the known codenames - const char *basename = 0; - switch (int(MacintoshVersion)) { - case MV_CHEETAH: - case MV_PUMA: - case MV_JAGUAR: - case MV_PANTHER: - case MV_TIGER: - // This version of Qt does not run on those versions of OS X - // so this case label will never be reached - Q_UNREACHABLE(); - break; - case MV_LEOPARD: - basename = "Mac OS X Leopard ("; - break; - case MV_SNOWLEOPARD: - basename = "Mac OS X Snow Leopard ("; - break; - case MV_LION: - basename = "Mac OS X Lion ("; - break; - case MV_MOUNTAINLION: - basename = "OS X Mountain Lion ("; - break; - case MV_MAVERICKS: - basename = "OS X Mavericks ("; - break; -#ifdef MV_YOSEMITE - case MV_YOSEMITE: -#else - case 0x000C: // MV_YOSEMITE -#endif - basename = "OS X Yosemite ("; - break; -#ifdef MV_ELCAPITAN - case MV_ELCAPITAN : -#else - case 0x000D: // MV_ELCAPITAN -#endif - basename = "OS X El Capitan ("; - break; - } - if (basename) - return QLatin1String(basename) + productVersion() + QLatin1Char(')'); - - // a future version of OS X - return QLatin1String("OS X ") + productVersion(); -#elif defined(Q_OS_WINPHONE) - return QLatin1String("Windows Phone ") + QLatin1String(winVer_helper()); -#elif defined(Q_OS_WIN) - return QLatin1String("Windows ") + QLatin1String(winVer_helper()); -#elif defined(Q_OS_ANDROID) - return QLatin1String("Android ") + productVersion(); -#elif defined(Q_OS_BLACKBERRY) - return QLatin1String("BlackBerry ") + productVersion(); -#elif defined(Q_OS_UNIX) -# ifdef USE_ETC_OS_RELEASE - QUnixOSVersion unixOsVersion; - readEtcOsRelease(unixOsVersion); - if (!unixOsVersion.prettyName.isEmpty()) - return unixOsVersion.prettyName; -# endif - struct utsname u; - if (uname(&u) == 0) - return QString::fromLatin1(u.sysname) + QLatin1Char(' ') + QString::fromLatin1(u.release); -#endif - return unknownText(); -} - -#endif // Qt >= 5.4 - -QString SubsurfaceSysInfo::prettyOsName() -{ - // Matches the pre-release version of Qt 5.4 - QString pretty = prettyProductName(); -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID) - // QSysInfo::kernelType() returns lowercase ("linux" instead of "Linux") - struct utsname u; - if (uname(&u) == 0) - return QString::fromLatin1(u.sysname) + QLatin1String(" (") + pretty + QLatin1Char(')'); -#endif - return pretty; -} - -extern "C" { -bool isWin7Or8() -{ -#ifdef Q_OS_WIN - return (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based) >= QSysInfo::WV_WINDOWS7; -#else - return false; -#endif -} -} diff --git a/subsurface-core/subsurfacesysinfo.h b/subsurface-core/subsurfacesysinfo.h deleted file mode 100644 index b2c267b83..000000000 --- a/subsurface-core/subsurfacesysinfo.h +++ /dev/null @@ -1,65 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Copyright (C) 2014 Intel Corporation -** Contact: http://www.qt-project.org/legal -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef SUBSURFACESYSINFO_H -#define SUBSURFACESYSINFO_H - -#include <QSysInfo> - -class SubsurfaceSysInfo : public QSysInfo { -public: -#if QT_VERSION < 0x050400 - static QString buildCpuArchitecture(); - static QString currentCpuArchitecture(); - static QString buildAbi(); - - static QString kernelType(); - static QString kernelVersion(); - static QString productType(); - static QString productVersion(); - static QString prettyProductName(); -#endif - static QString prettyOsName(); -}; - - -#endif // SUBSURFACESYSINFO_H diff --git a/subsurface-core/taxonomy.c b/subsurface-core/taxonomy.c deleted file mode 100644 index 670d85ad0..000000000 --- a/subsurface-core/taxonomy.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "taxonomy.h" -#include "gettext.h" -#include <stdlib.h> - -char *taxonomy_category_names[TC_NR_CATEGORIES] = { - QT_TRANSLATE_NOOP("getTextFromC", "None"), - QT_TRANSLATE_NOOP("getTextFromC", "Ocean"), - QT_TRANSLATE_NOOP("getTextFromC", "Country"), - QT_TRANSLATE_NOOP("getTextFromC", "State"), - QT_TRANSLATE_NOOP("getTextFromC", "County"), - QT_TRANSLATE_NOOP("getTextFromC", "Town"), - QT_TRANSLATE_NOOP("getTextFromC", "City") -}; - -// these are the names for geoname.org -char *taxonomy_api_names[TC_NR_CATEGORIES] = { - "none", - "name", - "countryName", - "adminName1", - "adminName2", - "toponymName", - "adminName3" -}; - -struct taxonomy *alloc_taxonomy() -{ - return calloc(TC_NR_CATEGORIES, sizeof(struct taxonomy)); -} - -void free_taxonomy(struct taxonomy_data *t) -{ - if (t) { - for (int i = 0; i < t->nr; i++) - free((void *)t->category[i].value); - free(t->category); - t->category = NULL; - t->nr = 0; - } -} - -int taxonomy_index_for_category(struct taxonomy_data *t, enum taxonomy_category cat) -{ - for (int i = 0; i < t->nr; i++) - if (t->category[i].category == cat) - return i; - return -1; -} diff --git a/subsurface-core/taxonomy.h b/subsurface-core/taxonomy.h deleted file mode 100644 index 51245d562..000000000 --- a/subsurface-core/taxonomy.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef TAXONOMY_H -#define TAXONOMY_H - -#ifdef __cplusplus -extern "C" { -#endif - -enum taxonomy_category { - TC_NONE, - TC_OCEAN, - TC_COUNTRY, - TC_ADMIN_L1, - TC_ADMIN_L2, - TC_LOCALNAME, - TC_ADMIN_L3, - TC_NR_CATEGORIES -}; - -extern char *taxonomy_category_names[TC_NR_CATEGORIES]; -extern char *taxonomy_api_names[TC_NR_CATEGORIES]; - -struct taxonomy { - int category; /* the category for this tag: ocean, country, admin_l1, admin_l2, localname, etc */ - const char *value; /* the value returned, parsed, or manually entered for that category */ - enum { GEOCODED, PARSED, MANUAL, COPIED } origin; -}; - -/* the data block contains 3 taxonomy structures - unused ones have a tag value of NONE */ -struct taxonomy_data { - int nr; - struct taxonomy *category; -}; - -struct taxonomy *alloc_taxonomy(); -void free_taxonomy(struct taxonomy_data *t); -int taxonomy_index_for_category(struct taxonomy_data *t, enum taxonomy_category cat); - -#ifdef __cplusplus -} -#endif -#endif // TAXONOMY_H diff --git a/subsurface-core/time.c b/subsurface-core/time.c deleted file mode 100644 index 0893f19d8..000000000 --- a/subsurface-core/time.c +++ /dev/null @@ -1,98 +0,0 @@ -#include <string.h> -#include "dive.h" - -/* - * Convert 64-bit timestamp to 'struct tm' in UTC. - * - * On 32-bit machines, only do 64-bit arithmetic for the seconds - * part, after that we do everything in 'long'. 64-bit divides - * are unnecessary once you're counting minutes (32-bit minutes: - * 8000+ years). - */ -void utc_mkdate(timestamp_t timestamp, struct tm *tm) -{ - static const unsigned int mdays[] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, - }; - static const unsigned int mdays_leap[] = { - 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, - }; - unsigned long val; - unsigned int leapyears; - int m; - const unsigned int *mp; - - memset(tm, 0, sizeof(*tm)); - - /* seconds since 1970 -> minutes since 1970 */ - tm->tm_sec = timestamp % 60; - val = timestamp /= 60; - - /* Do the simple stuff */ - tm->tm_min = val % 60; - val /= 60; - tm->tm_hour = val % 24; - val /= 24; - - /* Jan 1, 1970 was a Thursday (tm_wday=4) */ - tm->tm_wday = (val + 4) % 7; - - /* - * Now we're in "days since Jan 1, 1970". To make things easier, - * let's make it "days since Jan 1, 1968", since that's a leap-year - */ - val += 365 + 366; - - /* This only works up until 2099 (2100 isn't a leap-year) */ - leapyears = val / (365 * 4 + 1); - val %= (365 * 4 + 1); - tm->tm_year = 68 + leapyears * 4; - - /* Handle the leap-year itself */ - mp = mdays_leap; - if (val > 365) { - tm->tm_year++; - val -= 366; - tm->tm_year += val / 365; - val %= 365; - mp = mdays; - } - - for (m = 0; m < 12; m++) { - if (val < *mp) - break; - val -= *mp++; - } - tm->tm_mday = val + 1; - tm->tm_mon = m; -} - -timestamp_t utc_mktime(struct tm *tm) -{ - static const int mdays[] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 - }; - int year = tm->tm_year; - int month = tm->tm_mon; - int day = tm->tm_mday; - - /* First normalize relative to 1900 */ - if (year < 70) - year += 100; - else if (year > 1900) - year -= 1900; - - /* Normalized to Jan 1, 1970: unix time */ - year -= 70; - - if (year < 0 || year > 129) /* algo only works for 1970-2099 */ - return -1; - if (month < 0 || month > 11) /* array bounds */ - return -1; - if (month < 2 || (year + 2) % 4) - day--; - if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0) - return -1; - return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24 * 60 * 60UL + - tm->tm_hour * 60 * 60 + tm->tm_min * 60 + tm->tm_sec; -} diff --git a/subsurface-core/uemis-downloader.c b/subsurface-core/uemis-downloader.c deleted file mode 100644 index b9b532303..000000000 --- a/subsurface-core/uemis-downloader.c +++ /dev/null @@ -1,1403 +0,0 @@ -/* - * uemis-downloader.c - * - * Copyright (c) Dirk Hohndel <dirk@hohndel.org> - * released under GPL2 - * - * very (VERY) loosely based on the algorithms found in Java code by Fabian Gast <fgast@only640k.net> - * which was released under the BSD-STYLE BEER WARE LICENSE - * I believe that I only used the information about HOW to do this (download data from the Uemis - * Zurich) but did not actually use any of his copyrighted code, therefore the license under which - * he released his code does not apply to this new implementation in C - * - * Modified by Guido Lerch guido.lerch@gmail.com in August 2015 - */ -#include <fcntl.h> -#include <dirent.h> -#include <stdio.h> -#include <unistd.h> -#include <string.h> -#include <errno.h> - -#include "gettext.h" -#include "libdivecomputer.h" -#include "uemis.h" -#include "divelist.h" - -#define ERR_FS_ALMOST_FULL QT_TRANSLATE_NOOP("gettextFromC", "Uemis Zurich: the file system is almost full.\nDisconnect/reconnect the dive computer\nand click \'Retry\'") -#define ERR_FS_FULL QT_TRANSLATE_NOOP("gettextFromC", "Uemis Zurich: the file system is full.\nDisconnect/reconnect the dive computer\nand click Retry") -#define ERR_FS_SHORT_WRITE QT_TRANSLATE_NOOP("gettextFromC", "Short write to req.txt file.\nIs the Uemis Zurich plugged in correctly?") -#define ERR_NO_FILES QT_TRANSLATE_NOOP("gettextFromC", "No dives to download.") -#define BUFLEN 2048 -#define BUFLEN 2048 -#define NUM_PARAM_BUFS 10 - -// debugging setup -// #define UEMIS_DEBUG 1 + 2 + 4 + 8 + 16 + 32 - -#define UEMIS_MAX_FILES 4000 -#define UEMIS_MEM_FULL 1 -#define UEMIS_MEM_OK 0 -#define UEMIS_SPOT_BLOCK_SIZE 1 -#define UEMIS_DIVE_DETAILS_SIZE 2 -#define UEMIS_LOG_BLOCK_SIZE 10 -#define UEMIS_CHECK_LOG 1 -#define UEMIS_CHECK_DETAILS 2 -#define UEMIS_CHECK_SINGLE_DIVE 3 - -#if UEMIS_DEBUG -const char *home, *user, *d_time; -static int debug_round = 0; -#define debugfile stderr -#endif - -#if UEMIS_DEBUG & 64 /* we are reading from a copy of the filesystem, not the device - no need to wait */ -#define UEMIS_TIMEOUT 50 /* 50ns */ -#define UEMIS_LONG_TIMEOUT 500 /* 500ns */ -#define UEMIS_MAX_TIMEOUT 2000 /* 2ms */ -#else -#define UEMIS_TIMEOUT 50000 /* 50ms */ -#define UEMIS_LONG_TIMEOUT 500000 /* 500ms */ -#define UEMIS_MAX_TIMEOUT 2000000 /* 2s */ -#endif - -static char *param_buff[NUM_PARAM_BUFS]; -static char *reqtxt_path; -static int reqtxt_file; -static int filenr; -static int number_of_files; -static char *mbuf = NULL; -static int mbuf_size = 0; - -static int max_mem_used = -1; -static int next_table_index = 0; -static int dive_to_read = 0; - -static int max_deleted_seen = -1; - -/* helper function to parse the Uemis data structures */ -static void uemis_ts(char *buffer, void *_when) -{ - struct tm tm; - timestamp_t *when = _when; - - memset(&tm, 0, sizeof(tm)); - sscanf(buffer, "%d-%d-%dT%d:%d:%d", - &tm.tm_year, &tm.tm_mon, &tm.tm_mday, - &tm.tm_hour, &tm.tm_min, &tm.tm_sec); - tm.tm_mon -= 1; - tm.tm_year -= 1900; - *when = utc_mktime(&tm); -} - -/* float minutes */ -static void uemis_duration(char *buffer, duration_t *duration) -{ - duration->seconds = rint(ascii_strtod(buffer, NULL) * 60); -} - -/* int cm */ -static void uemis_depth(char *buffer, depth_t *depth) -{ - depth->mm = atoi(buffer) * 10; -} - -static void uemis_get_index(char *buffer, int *idx) -{ - *idx = atoi(buffer); -} - -/* space separated */ -static void uemis_add_string(const char *buffer, char **text, const char *delimit) -{ - /* do nothing if this is an empty buffer (Uemis sometimes returns a single - * space for empty buffers) */ - if (!buffer || !*buffer || (*buffer == ' ' && *(buffer + 1) == '\0')) - return; - if (!*text) { - *text = strdup(buffer); - } else { - char *buf = malloc(strlen(buffer) + strlen(*text) + 2); - strcpy(buf, *text); - strcat(buf, delimit); - strcat(buf, buffer); - free(*text); - *text = buf; - } -} - -/* still unclear if it ever reports lbs */ -static void uemis_get_weight(char *buffer, weightsystem_t *weight, int diveid) -{ - weight->weight.grams = uemis_get_weight_unit(diveid) ? - lbs_to_grams(ascii_strtod(buffer, NULL)) : - ascii_strtod(buffer, NULL) * 1000; - weight->description = strdup(translate("gettextFromC", "unknown")); -} - -static struct dive *uemis_start_dive(uint32_t deviceid) -{ - struct dive *dive = alloc_dive(); - dive->downloaded = true; - dive->dc.model = strdup("Uemis Zurich"); - dive->dc.deviceid = deviceid; - return dive; -} - -static struct dive *get_dive_by_uemis_diveid(device_data_t *devdata, uint32_t object_id) -{ - for (int i = 0; i < devdata->download_table->nr; i++) { - if (object_id == devdata->download_table->dives[i]->dc.diveid) - return devdata->download_table->dives[i]; - } - return NULL; -} - -static void record_uemis_dive(device_data_t *devdata, struct dive *dive) -{ - if (devdata->create_new_trip) { - if (!devdata->trip) - devdata->trip = create_and_hookup_trip_from_dive(dive); - else - add_dive_to_trip(dive, devdata->trip); - } - record_dive_to_table(dive, devdata->download_table); -} - -/* send text to the importer progress bar */ -static void uemis_info(const char *fmt, ...) -{ - static char buffer[256]; - va_list ap; - - va_start(ap, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, ap); - va_end(ap); - progress_bar_text = buffer; - if (verbose) - fprintf(stderr, "Uemis downloader: %s\n", buffer); -} - -static long bytes_available(int file) -{ - long result; - long now = lseek(file, 0, SEEK_CUR); - if (now == -1) - return 0; - result = lseek(file, 0, SEEK_END); - lseek(file, now, SEEK_SET); - if (now == -1 || result == -1) - return 0; - return result; -} - -static int number_of_file(char *path) -{ - int count = 0; -#ifdef WIN32 - struct _wdirent *entry; - _WDIR *dirp = (_WDIR *)subsurface_opendir(path); -#else - struct dirent *entry; - DIR *dirp = (DIR *)subsurface_opendir(path); -#endif - - while (dirp) { -#ifdef WIN32 - entry = _wreaddir(dirp); - if (!entry) - break; -#else - entry = readdir(dirp); - if (!entry) - break; - if (strstr(entry->d_name, ".TXT") || strstr(entry->d_name, ".txt")) /* If the entry is a regular file */ -#endif - count++; - } -#ifdef WIN32 - _wclosedir(dirp); -#else - closedir(dirp); -#endif - return count; -} - -static char *build_filename(const char *path, const char *name) -{ - int len = strlen(path) + strlen(name) + 2; - char *buf = malloc(len); -#if WIN32 - snprintf(buf, len, "%s\\%s", path, name); -#else - snprintf(buf, len, "%s/%s", path, name); -#endif - return buf; -} - -/* Check if there's a req.txt file and get the starting filenr from it. - * Test for the maximum number of ANS files (I believe this is always - * 4000 but in case there are differences depending on firmware, this - * code is easy enough */ -static bool uemis_init(const char *path) -{ - char *ans_path; - int i; - - if (!path) - return false; - /* let's check if this is indeed a Uemis DC */ - reqtxt_path = build_filename(path, "req.txt"); - reqtxt_file = subsurface_open(reqtxt_path, O_RDONLY | O_CREAT, 0666); - if (reqtxt_file < 0) { -#if UEMIS_DEBUG & 1 - fprintf(debugfile, ":EE req.txt can't be opened\n"); -#endif - return false; - } - if (bytes_available(reqtxt_file) > 5) { - char tmp[6]; - read(reqtxt_file, tmp, 5); - tmp[5] = '\0'; -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "::r req.txt \"%s\"\n", tmp); -#endif - if (sscanf(tmp + 1, "%d", &filenr) != 1) - return false; - } else { - filenr = 0; -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "::r req.txt skipped as there were fewer than 5 bytes\n"); -#endif - } - close(reqtxt_file); - - /* It would be nice if we could simply go back to the first set of - * ANS files. But with a FAT filesystem that isn't possible */ - ans_path = build_filename(path, "ANS"); - number_of_files = number_of_file(ans_path); - free(ans_path); - /* initialize the array in which we collect the answers */ - for (i = 0; i < NUM_PARAM_BUFS; i++) - param_buff[i] = ""; - return true; -} - -static void str_append_with_delim(char *s, char *t) -{ - int len = strlen(s); - snprintf(s + len, BUFLEN - len, "%s{", t); -} - -/* The communication protocol with the DC is truly funky. - * After you write your request to the req.txt file you call this function. - * It writes the number of the next ANS file at the beginning of the req.txt - * file (prefixed by 'n' or 'r') and then again at the very end of it, after - * the full request (this time without the prefix). - * Then it syncs (not needed on Windows) and closes the file. */ -static void trigger_response(int file, char *command, int nr, long tailpos) -{ - char fl[10]; - - snprintf(fl, 8, "%s%04d", command, nr); -#if UEMIS_DEBUG & 4 - fprintf(debugfile, ":tr %s (after seeks)\n", fl); -#endif - if (lseek(file, 0, SEEK_SET) == -1) - goto fs_error; - if (write(file, fl, strlen(fl)) == -1) - goto fs_error; - if (lseek(file, tailpos, SEEK_SET) == -1) - goto fs_error; - if (write(file, fl + 1, strlen(fl + 1)) == -1) - goto fs_error; -#ifndef WIN32 - fsync(file); -#endif -fs_error: - close(file); -} - -static char *next_token(char **buf) -{ - char *q, *p = strchr(*buf, '{'); - if (p) - *p = '\0'; - else - p = *buf + strlen(*buf) - 1; - q = *buf; - *buf = p + 1; - return q; -} - -/* poor man's tokenizer that understands a quoted delimiter ('{') */ -static char *next_segment(char *buf, int *offset, int size) -{ - int i = *offset; - int seg_size; - bool done = false; - char *segment; - - while (!done) { - if (i < size) { - if (i < size - 1 && buf[i] == '\\' && - (buf[i + 1] == '\\' || buf[i + 1] == '{')) - memcpy(buf + i, buf + i + 1, size - i - 1); - else if (buf[i] == '{') - done = true; - i++; - } else { - done = true; - } - } - seg_size = i - *offset - 1; - if (seg_size < 0) - seg_size = 0; - segment = malloc(seg_size + 1); - memcpy(segment, buf + *offset, seg_size); - segment[seg_size] = '\0'; - *offset = i; - return segment; -} - -/* a dynamically growing buffer to store the potentially massive responses. - * The binary data block can be more than 100k in size (base64 encoded) */ -static void buffer_add(char **buffer, int *buffer_size, char *buf) -{ - if (!buf) - return; - if (!*buffer) { - *buffer = strdup(buf); - *buffer_size = strlen(*buffer) + 1; - } else { - *buffer_size += strlen(buf); - *buffer = realloc(*buffer, *buffer_size); - strcat(*buffer, buf); - } -#if UEMIS_DEBUG & 8 - fprintf(debugfile, "added \"%s\" to buffer - new length %d\n", buf, *buffer_size); -#endif -} - -/* are there more ANS files we can check? */ -static bool next_file(int max) -{ - if (filenr >= max) - return false; - filenr++; - return true; -} - -/* try and do a quick decode - without trying to get to fancy in case the data - * straddles a block boundary... - * we are parsing something that looks like this: - * object_id{int{2{date{ts{2011-04-05T12:38:04{duration{float{12.000... - */ -static char *first_object_id_val(char *buf) -{ - char *object, *bufend; - if (!buf) - return NULL; - bufend = buf + strlen(buf); - object = strstr(buf, "object_id"); - if (object && object + 14 < bufend) { - /* get the value */ - char tmp[36]; - char *p = object + 14; - char *t = tmp; - -#if UEMIS_DEBUG & 4 - char debugbuf[50]; - strncpy(debugbuf, object, 49); - debugbuf[49] = '\0'; - fprintf(debugfile, "buf |%s|\n", debugbuf); -#endif - while (p < bufend && *p != '{' && t < tmp + 9) - *t++ = *p++; - if (*p == '{') { - /* found the object_id - let's quickly look for the date */ - if (strncmp(p, "{date{ts{", 9) == 0 && strstr(p, "{duration{") != NULL) { - /* cool - that was easy */ - *t++ = ','; - *t++ = ' '; - /* skip the 9 characters we just found and take the date, ignoring the seconds - * and replace the silly 'T' in the middle with a space */ - strncpy(t, p + 9, 16); - if (*(t + 10) == 'T') - *(t + 10) = ' '; - t += 16; - } - *t = '\0'; - return strdup(tmp); - } - } - return NULL; -} - -/* ultra-simplistic; it doesn't deal with the case when the object_id is - * split across two chunks. It also doesn't deal with the discrepancy between - * object_id and dive number as understood by the dive computer */ -static void show_progress(char *buf, const char *what) -{ - char *val = first_object_id_val(buf); - if (val) { -/* let the user know what we are working on */ -#if UEMIS_DEBUG & 8 - fprintf(debugfile, "reading %s\n %s\n %s\n", what, val, buf); -#endif - uemis_info(translate("gettextFromC", "%s %s"), what, val); - free(val); - } -} - -static void uemis_increased_timeout(int *timeout) -{ - if (*timeout < UEMIS_MAX_TIMEOUT) - *timeout += UEMIS_LONG_TIMEOUT; - usleep(*timeout); -} - -/* send a request to the dive computer and collect the answer */ -static bool uemis_get_answer(const char *path, char *request, int n_param_in, - int n_param_out, const char **error_text) -{ - int i = 0, file_length; - char sb[BUFLEN]; - char fl[13]; - char tmp[101]; - const char *what = translate("gettextFromC", "data"); - bool searching = true; - bool assembling_mbuf = false; - bool ismulti = false; - bool found_answer = false; - bool more_files = true; - bool answer_in_mbuf = false; - char *ans_path; - int ans_file; - int timeout = UEMIS_LONG_TIMEOUT; - - reqtxt_file = subsurface_open(reqtxt_path, O_RDWR | O_CREAT, 0666); - if (reqtxt_file == -1) { - *error_text = "can't open req.txt"; -#ifdef UEMIS_DEBUG - fprintf(debugfile, "open %s failed with errno %d\n", reqtxt_path, errno); -#endif - return false; - } - snprintf(sb, BUFLEN, "n%04d12345678", filenr); - str_append_with_delim(sb, request); - for (i = 0; i < n_param_in; i++) - str_append_with_delim(sb, param_buff[i]); - if (!strcmp(request, "getDivelogs") || !strcmp(request, "getDeviceData") || !strcmp(request, "getDirectory") || - !strcmp(request, "getDivespot") || !strcmp(request, "getDive")) { - answer_in_mbuf = true; - str_append_with_delim(sb, ""); - if (!strcmp(request, "getDivelogs")) - what = translate("gettextFromC", "divelog #"); - else if (!strcmp(request, "getDivespot")) - what = translate("gettextFromC", "divespot #"); - else if (!strcmp(request, "getDive")) - what = translate("gettextFromC", "details for #"); - } - str_append_with_delim(sb, ""); - file_length = strlen(sb); - snprintf(fl, 10, "%08d", file_length - 13); - memcpy(sb + 5, fl, strlen(fl)); -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "::w req.txt \"%s\"\n", sb); -#endif - int written = write(reqtxt_file, sb, strlen(sb)); - if (written == -1 || (size_t)written != strlen(sb)) { - *error_text = translate("gettextFromC", ERR_FS_SHORT_WRITE); - return false; - } - if (!next_file(number_of_files)) { - *error_text = translate("gettextFromC", ERR_FS_FULL); - more_files = false; - } - trigger_response(reqtxt_file, "n", filenr, file_length); - usleep(timeout); - free(mbuf); - mbuf = NULL; - mbuf_size = 0; - while (searching || assembling_mbuf) { - if (import_thread_cancelled) - return false; - progress_bar_fraction = filenr / (double)UEMIS_MAX_FILES; - snprintf(fl, 13, "ANS%d.TXT", filenr - 1); - ans_path = build_filename(build_filename(path, "ANS"), fl); - ans_file = subsurface_open(ans_path, O_RDONLY, 0666); - read(ans_file, tmp, 100); - close(ans_file); -#if UEMIS_DEBUG & 8 - tmp[100] = '\0'; - fprintf(debugfile, "::t %s \"%s\"\n", ans_path, tmp); -#elif UEMIS_DEBUG & 4 - char pbuf[4]; - pbuf[0] = tmp[0]; - pbuf[1] = tmp[1]; - pbuf[2] = tmp[2]; - pbuf[3] = 0; - fprintf(debugfile, "::t %s \"%s...\"\n", ans_path, pbuf); -#endif - free(ans_path); - if (tmp[0] == '1') { - searching = false; - if (tmp[1] == 'm') { - assembling_mbuf = true; - ismulti = true; - } - if (tmp[2] == 'e') - assembling_mbuf = false; - if (assembling_mbuf) { - if (!next_file(number_of_files)) { - *error_text = translate("gettextFromC", ERR_FS_FULL); - more_files = false; - assembling_mbuf = false; - } - reqtxt_file = subsurface_open(reqtxt_path, O_RDWR | O_CREAT, 0666); - if (reqtxt_file == -1) { - *error_text = "can't open req.txt"; - fprintf(stderr, "open %s failed with errno %d\n", reqtxt_path, errno); - return false; - } - trigger_response(reqtxt_file, "n", filenr, file_length); - } - } else { - if (!next_file(number_of_files - 1)) { - *error_text = translate("gettextFromC", ERR_FS_FULL); - more_files = false; - assembling_mbuf = false; - searching = false; - } - reqtxt_file = subsurface_open(reqtxt_path, O_RDWR | O_CREAT, 0666); - if (reqtxt_file == -1) { - *error_text = "can't open req.txt"; - fprintf(stderr, "open %s failed with errno %d\n", reqtxt_path, errno); - return false; - } - trigger_response(reqtxt_file, "r", filenr, file_length); - uemis_increased_timeout(&timeout); - } - if (ismulti && more_files && tmp[0] == '1') { - int size; - snprintf(fl, 13, "ANS%d.TXT", assembling_mbuf ? filenr - 2 : filenr - 1); - ans_path = build_filename(build_filename(path, "ANS"), fl); - ans_file = subsurface_open(ans_path, O_RDONLY, 0666); - free(ans_path); - size = bytes_available(ans_file); - if (size > 3) { - char *buf; - int r; - if (lseek(ans_file, 3, SEEK_CUR) == -1) - goto fs_error; - buf = malloc(size - 2); - if ((r = read(ans_file, buf, size - 3)) != size - 3) { - free(buf); - goto fs_error; - } - buf[r] = '\0'; - buffer_add(&mbuf, &mbuf_size, buf); - show_progress(buf, what); - free(buf); - param_buff[3]++; - } - close(ans_file); - timeout = UEMIS_TIMEOUT; - usleep(UEMIS_TIMEOUT); - } - } - if (more_files) { - int size = 0, j = 0; - char *buf = NULL; - - if (!ismulti) { - snprintf(fl, 13, "ANS%d.TXT", filenr - 1); - ans_path = build_filename(build_filename(path, "ANS"), fl); - ans_file = subsurface_open(ans_path, O_RDONLY, 0666); - free(ans_path); - size = bytes_available(ans_file); - if (size > 3) { - int r; - if (lseek(ans_file, 3, SEEK_CUR) == -1) - goto fs_error; - buf = malloc(size - 2); - if ((r = read(ans_file, buf, size - 3)) != size - 3) { - free(buf); - goto fs_error; - } - buf[r] = '\0'; - buffer_add(&mbuf, &mbuf_size, buf); - show_progress(buf, what); -#if UEMIS_DEBUG & 8 - fprintf(debugfile, "::r %s \"%s\"\n", fl, buf); -#endif - } - size -= 3; - close(ans_file); - } else { - ismulti = false; - } -#if UEMIS_DEBUG & 8 - fprintf(debugfile, ":r: %s\n", buf); -#endif - if (!answer_in_mbuf) - for (i = 0; i < n_param_out && j < size; i++) - param_buff[i] = next_segment(buf, &j, size); - found_answer = true; - free(buf); - } -#if UEMIS_DEBUG & 1 - for (i = 0; i < n_param_out; i++) - fprintf(debugfile, "::: %d: %s\n", i, param_buff[i]); -#endif - return found_answer; -fs_error: - close(ans_file); - return false; -} - -static bool parse_divespot(char *buf) -{ - char *bp = buf + 1; - char *tp = next_token(&bp); - char *tag, *type, *val; - char locationstring[1024] = ""; - int divespot, len; - double latitude = 0.0, longitude = 0.0; - - // dive spot got deleted, so fail here - if (strstr(bp, "deleted{bool{true")) - return false; - // not a dive spot, fail here - if (strcmp(tp, "divespot")) - return false; - do - tag = next_token(&bp); - while (*tag && strcmp(tag, "object_id")); - if (!*tag) - return false; - next_token(&bp); - val = next_token(&bp); - divespot = atoi(val); - do { - tag = next_token(&bp); - type = next_token(&bp); - val = next_token(&bp); - if (!strcmp(type, "string") && *val && strcmp(val, " ")) { - len = strlen(locationstring); - snprintf(locationstring + len, sizeof(locationstring) - len, - "%s%s", len ? ", " : "", val); - } else if (!strcmp(type, "float")) { - if (!strcmp(tag, "longitude")) - longitude = ascii_strtod(val, NULL); - else if (!strcmp(tag, "latitude")) - latitude = ascii_strtod(val, NULL); - } - } while (tag && *tag); - - uemis_set_divelocation(divespot, locationstring, longitude, latitude); - return true; -} - -static char *suit[] = {"", QT_TRANSLATE_NOOP("gettextFromC", "wetsuit"), QT_TRANSLATE_NOOP("gettextFromC", "semidry"), QT_TRANSLATE_NOOP("gettextFromC", "drysuit")}; -static char *suit_type[] = {"", QT_TRANSLATE_NOOP("gettextFromC", "shorty"), QT_TRANSLATE_NOOP("gettextFromC", "vest"), QT_TRANSLATE_NOOP("gettextFromC", "long john"), QT_TRANSLATE_NOOP("gettextFromC", "jacket"), QT_TRANSLATE_NOOP("gettextFromC", "full suit"), QT_TRANSLATE_NOOP("gettextFromC", "2 pcs full suit")}; -static char *suit_thickness[] = {"", "0.5-2mm", "2-3mm", "3-5mm", "5-7mm", "8mm+", QT_TRANSLATE_NOOP("gettextFromC", "membrane")}; - -static void parse_tag(struct dive *dive, char *tag, char *val) -{ - /* we can ignore computer_id, water and gas as those are redundant - * with the binary data and would just get overwritten */ -#if UEMIS_DEBUG & 4 - if (strcmp(tag, "file_content")) - fprintf(debugfile, "Adding to dive %d : %s = %s\n", dive->dc.diveid, tag, val); -#endif - if (!strcmp(tag, "date")) { - uemis_ts(val, &dive->when); - } else if (!strcmp(tag, "duration")) { - uemis_duration(val, &dive->dc.duration); - } else if (!strcmp(tag, "depth")) { - uemis_depth(val, &dive->dc.maxdepth); - } else if (!strcmp(tag, "file_content")) { - uemis_parse_divelog_binary(val, dive); - } else if (!strcmp(tag, "altitude")) { - uemis_get_index(val, &dive->dc.surface_pressure.mbar); - } else if (!strcmp(tag, "f32Weight")) { - uemis_get_weight(val, &dive->weightsystem[0], dive->dc.diveid); - } else if (!strcmp(tag, "notes")) { - uemis_add_string(val, &dive->notes, " "); - } else if (!strcmp(tag, "u8DiveSuit")) { - if (*suit[atoi(val)]) - uemis_add_string(translate("gettextFromC", suit[atoi(val)]), &dive->suit, " "); - } else if (!strcmp(tag, "u8DiveSuitType")) { - if (*suit_type[atoi(val)]) - uemis_add_string(translate("gettextFromC", suit_type[atoi(val)]), &dive->suit, " "); - } else if (!strcmp(tag, "u8SuitThickness")) { - if (*suit_thickness[atoi(val)]) - uemis_add_string(translate("gettextFromC", suit_thickness[atoi(val)]), &dive->suit, " "); - } else if (!strcmp(tag, "nickname")) { - uemis_add_string(val, &dive->buddy, ","); - } -} - -static bool uemis_delete_dive(device_data_t *devdata, uint32_t diveid) -{ - struct dive *dive = NULL; - - if (devdata->download_table->dives[devdata->download_table->nr - 1]->dc.diveid == diveid) { - /* we hit the last one in the array */ - dive = devdata->download_table->dives[devdata->download_table->nr - 1]; - } else { - for (int i = 0; i < devdata->download_table->nr - 1; i++) { - if (devdata->download_table->dives[i]->dc.diveid == diveid) { - dive = devdata->download_table->dives[i]; - for (int x = i; x < devdata->download_table->nr - 1; x++) - devdata->download_table->dives[i] = devdata->download_table->dives[x + 1]; - } - } - } - if (dive) { - devdata->download_table->dives[--devdata->download_table->nr] = NULL; - if (dive->tripflag != TF_NONE) - remove_dive_from_trip(dive, false); - - free(dive->dc.sample); - free((void *)dive->notes); - free((void *)dive->divemaster); - free((void *)dive->buddy); - free((void *)dive->suit); - taglist_free(dive->tag_list); - free(dive); - - return true; - } - return false; -} - -/* This function is called for both divelog and dive information that we get - * from the SDA (what an insane design, btw). The object_id in the divelog - * matches the logfilenr in the dive information (which has its own, often - * different object_id) - we use this as the diveid. - * We create the dive when parsing the divelog and then later, when we parse - * the dive information we locate the already created dive via its diveid. - * Most things just get parsed and converted into our internal data structures, - * but the dive location API is even more crazy. We just get an id that is an - * index into yet another data store that we read out later. In order to - * correctly populate the location and gps data from that we need to remember - * the addresses of those fields for every dive that references the divespot. */ -static bool process_raw_buffer(device_data_t *devdata, uint32_t deviceid, char *inbuf, char **max_divenr, int *for_dive) -{ - char *buf = strdup(inbuf); - char *tp, *bp, *tag, *type, *val; - bool done = false; - int inbuflen = strlen(inbuf); - char *endptr = buf + inbuflen; - bool is_log = false, is_dive = false; - char *sections[10]; - size_t s, nr_sections = 0; - struct dive *dive = NULL; - char dive_no[10]; - -#if UEMIS_DEBUG & 8 - fprintf(debugfile, "p_r_b %s\n", inbuf); -#endif - if (for_dive) - *for_dive = -1; - bp = buf + 1; - tp = next_token(&bp); - if (strcmp(tp, "divelog") == 0) { - /* this is a divelog */ - is_log = true; - tp = next_token(&bp); - /* is it a valid entry or nothing ? */ - if (strcmp(tp, "1.0") != 0 || strstr(inbuf, "divelog{1.0{{{{")) { - free(buf); - return false; - } - } else if (strcmp(tp, "dive") == 0) { - /* this is dive detail */ - is_dive = true; - tp = next_token(&bp); - if (strcmp(tp, "1.0") != 0) { - free(buf); - return false; - } - } else { - /* don't understand the buffer */ - free(buf); - return false; - } - if (is_log) { - dive = uemis_start_dive(deviceid); - } else { - /* remember, we don't know if this is the right entry, - * so first test if this is even a valid entry */ - if (strstr(inbuf, "deleted{bool{true")) { -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "p_r_b entry deleted\n"); -#endif - /* oops, this one isn't valid, suggest to try the previous one */ - free(buf); - return false; - } - /* quickhack and workaround to capture the original dive_no - * I am doing this so I don't have to change the original design - * but when parsing a dive we never parse the dive number because - * at the time it's being read the *dive variable is not set because - * the dive_no tag comes before the object_id in the uemis ans file - */ - dive_no[0] = '\0'; - char *dive_no_buf = strdup(inbuf); - char *dive_no_ptr = strstr(dive_no_buf, "dive_no{int{") + 12; - if (dive_no_ptr) { - char *dive_no_end = strstr(dive_no_ptr, "{"); - if (dive_no_end) { - *dive_no_end = '\0'; - strncpy(dive_no, dive_no_ptr, 9); - dive_no[9] = '\0'; - } - } - free(dive_no_buf); - } - while (!done) { - /* the valid buffer ends with a series of delimiters */ - if (bp >= endptr - 2 || !strcmp(bp, "{{")) - break; - tag = next_token(&bp); - /* we also end if we get an empty tag */ - if (*tag == '\0') - break; - for (s = 0; s < nr_sections; s++) - if (!strcmp(tag, sections[s])) { - tag = next_token(&bp); - break; - } - type = next_token(&bp); - if (!strcmp(type, "1.0")) { - /* this tells us the sections that will follow; the tag here - * is of the format dive-<section> */ - sections[nr_sections] = strchr(tag, '-') + 1; -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "Expect to find section %s\n", sections[nr_sections]); -#endif - if (nr_sections < sizeof(sections) - 1) - nr_sections++; - continue; - } - val = next_token(&bp); -#if UEMIS_DEBUG & 8 - if (strlen(val) < 20) - fprintf(debugfile, "Parsed %s, %s, %s\n*************************\n", tag, type, val); -#endif - if (is_log && strcmp(tag, "object_id") == 0) { - free(*max_divenr); - *max_divenr = strdup(val); - dive->dc.diveid = atoi(val); -#if UEMIS_DEBUG % 2 - fprintf(debugfile, "Adding new dive from log with object_id %d.\n", atoi(val)); -#endif - } else if (is_dive && strcmp(tag, "logfilenr") == 0) { - /* this one tells us which dive we are adding data to */ - dive = get_dive_by_uemis_diveid(devdata, atoi(val)); - if (strcmp(dive_no, "0")) - dive->number = atoi(dive_no); - if (for_dive) - *for_dive = atoi(val); - } else if (!is_log && dive && !strcmp(tag, "divespot_id")) { - int divespot_id = atoi(val); - if (divespot_id != -1) { - dive->dive_site_uuid = create_dive_site("from Uemis", dive->when); - uemis_mark_divelocation(dive->dc.diveid, divespot_id, dive->dive_site_uuid); - } -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "Created divesite %d for diveid : %d\n", dive->dive_site_uuid, dive->dc.diveid); -#endif - } else if (dive) { - parse_tag(dive, tag, val); - } - if (is_log && !strcmp(tag, "file_content")) - done = true; - /* done with one dive (got the file_content tag), but there could be more: - * a '{' indicates the end of the record - but we need to see another "{{" - * later in the buffer to know that the next record is complete (it could - * be a short read because of some error */ - if (done && ++bp < endptr && *bp != '{' && strstr(bp, "{{")) { - done = false; - record_uemis_dive(devdata, dive); - mark_divelist_changed(true); - dive = uemis_start_dive(deviceid); - } - } - if (is_log) { - if (dive->dc.diveid) { - record_uemis_dive(devdata, dive); - mark_divelist_changed(true); - } else { /* partial dive */ - free(dive); - free(buf); - return false; - } - } - free(buf); - return true; -} - -static char *uemis_get_divenr(char *deviceidstr, int force) -{ - uint32_t deviceid, maxdiveid = 0; - int i; - char divenr[10]; - struct dive_table *table; - deviceid = atoi(deviceidstr); - - /* - * If we are are retrying after a disconnect/reconnect, we - * will look up the highest dive number in the dives we - * already have. - * - * Also, if "force_download" is true, do this even if we - * don't have any dives (maxdiveid will remain zero) - */ - if (force || downloadTable.nr) - table = &downloadTable; - else - table = &dive_table; - - for (i = 0; i < table->nr; i++) { - struct dive *d = table->dives[i]; - struct divecomputer *dc; - if (!d) - continue; - for_each_dc (d, dc) { - if (dc->model && !strcmp(dc->model, "Uemis Zurich") && - (dc->deviceid == 0 || dc->deviceid == 0x7fffffff || dc->deviceid == deviceid) && - dc->diveid > maxdiveid) - maxdiveid = dc->diveid; - } - } - if (max_deleted_seen >= 0 && maxdiveid < (uint32_t)max_deleted_seen) { - maxdiveid = max_deleted_seen; -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "overriding max seen with max deleted seen %d\n", max_deleted_seen); -#endif - } - snprintf(divenr, 10, "%d", maxdiveid); - return strdup(divenr); -} - -#if UEMIS_DEBUG -static int bufCnt = 0; -static bool do_dump_buffer_to_file(char *buf, char *prefix) -{ - char path[100]; - char date[40]; - char obid[40]; - if (!buf) - return false; - - if (strstr(buf, "date{ts{")) - strncpy(date, strstr(buf, "date{ts{"), sizeof(date)); - else - strncpy(date, "date{ts{no-date{", sizeof(date)); - - if (!strstr(buf, "object_id{int{")) - return false; - - strncpy(obid, strstr(buf, "object_id{int{"), sizeof(obid)); - char *ptr1 = strstr(date, "date{ts{"); - char *ptr2 = strstr(obid, "object_id{int{"); - char *pdate = next_token(&ptr1); - pdate = next_token(&ptr1); - pdate = next_token(&ptr1); - char *pobid = next_token(&ptr2); - pobid = next_token(&ptr2); - pobid = next_token(&ptr2); - snprintf(path, sizeof(path), "/%s/%s/UEMIS Dump/%s_%s_Uemis_dump_%s_in_round_%d_%d.txt", home, user, prefix, pdate, pobid, debug_round, bufCnt); - int dumpFile = subsurface_open(path, O_RDWR | O_CREAT, 0666); - if (dumpFile == -1) - return false; - write(dumpFile, buf, strlen(buf)); - close(dumpFile); - bufCnt++; - return true; -} -#endif - -/* do some more sophisticated calculations here to try and predict if the next round of - * divelog/divedetail reads will fit into the UEMIS buffer, - * filenr holds now the uemis filenr after having read several logs including the dive details, - * fCapacity will five us the average number of files needed for all currently loaded data - * remember the maximum file usage per dive - * return : UEMIS_MEM_OK if there is enough memory for a full round - * UEMIS_MEM_CRITICAL if the memory is good for reading the dive logs - * UEMIS_MEM_FULL if the memory is exhausted - */ -static int get_memory(struct dive_table *td, int checkpoint) -{ - if (td->nr <= 0) - return UEMIS_MEM_OK; - - switch (checkpoint) { - case UEMIS_CHECK_LOG: - if (filenr / td->nr > max_mem_used) - max_mem_used = filenr / td->nr; - - /* check if a full block of dive logs + dive details and dive spot fit into the UEMIS buffer */ -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "max_mem_used %d (from td->nr %d) * block_size %d > max_files %d - filenr %d?\n", max_mem_used, td->nr, UEMIS_LOG_BLOCK_SIZE, UEMIS_MAX_FILES, filenr); -#endif - if (max_mem_used * UEMIS_LOG_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) - return UEMIS_MEM_FULL; - break; - case UEMIS_CHECK_DETAILS: - /* check if the next set of dive details and dive spot fit into the UEMIS buffer */ - if ((UEMIS_DIVE_DETAILS_SIZE + UEMIS_SPOT_BLOCK_SIZE) * UEMIS_LOG_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) - return UEMIS_MEM_FULL; - break; - case UEMIS_CHECK_SINGLE_DIVE: - if (UEMIS_DIVE_DETAILS_SIZE + UEMIS_SPOT_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) - return UEMIS_MEM_FULL; - break; - } - return UEMIS_MEM_OK; -} - -/* mark a dive as deleted by setting download to false - * this will be picked up by some cleaning statement later */ -static void do_delete_dives(struct dive_table *td, int idx) -{ - for (int x = idx; x < td->nr; x++) - td->dives[x]->downloaded = false; -} - -static bool load_uemis_divespot(const char *mountpath, int divespot_id) -{ - char divespotnr[10]; - snprintf(divespotnr, sizeof(divespotnr), "%d", divespot_id); - param_buff[2] = divespotnr; -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "getDivespot %d\n", divespot_id); -#endif - bool success = uemis_get_answer(mountpath, "getDivespot", 3, 0, NULL); - if (mbuf && success) { -#if UEMIS_DEBUG & 16 - do_dump_buffer_to_file(mbuf, "Spot"); -#endif - return parse_divespot(mbuf); - } - return false; -} - -static void get_uemis_divespot(const char *mountpath, int divespot_id, struct dive *dive) -{ - struct dive_site *nds = get_dive_site_by_uuid(dive->dive_site_uuid); - if (nds && nds->name && strstr(nds->name,"from Uemis")) { - if (load_uemis_divespot(mountpath, divespot_id)) { - /* get the divesite based on the diveid, this should give us - * the newly created site - */ - struct dive_site *ods = NULL; - /* with the divesite name we got from parse_dive, that is called on load_uemis_divespot - * we search all existing divesites if we have one with the same name already. The function - * returns the first found which is luckily not the newly created. - */ - (void)get_dive_site_uuid_by_name(nds->name, &ods); - if (ods) { - /* if the uuid's are the same, the new site is a duplicate and can be deleted */ - if (nds->uuid != ods->uuid) { - delete_dive_site(nds->uuid); - dive->dive_site_uuid = ods->uuid; - } - } - } else { - /* if we can't load the dive site details, delete the site we - * created in process_raw_buffer - */ - delete_dive_site(dive->dive_site_uuid); - } - } -} - -static bool get_matching_dive(int idx, char *newmax, int *uemis_mem_status, struct device_data_t *data, const char *mountpath, const char deviceidnr) -{ - struct dive *dive = data->download_table->dives[idx]; - char log_file_no_to_find[20]; - char dive_to_read_buf[10]; - bool found = false; - bool found_below = false; - bool found_above = false; - int deleted_files = 0; - - snprintf(log_file_no_to_find, sizeof(log_file_no_to_find), "logfilenr{int{%d", dive->dc.diveid); -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "Looking for dive details to go with divelog id %d\n", dive->dc.diveid); -#endif - while (!found) { - if (import_thread_cancelled) - break; - snprintf(dive_to_read_buf, sizeof(dive_to_read_buf), "%d", dive_to_read); - param_buff[2] = dive_to_read_buf; - (void)uemis_get_answer(mountpath, "getDive", 3, 0, NULL); -#if UEMIS_DEBUG & 16 - do_dump_buffer_to_file(mbuf, "Dive"); -#endif - *uemis_mem_status = get_memory(data->download_table, UEMIS_CHECK_SINGLE_DIVE); - if (*uemis_mem_status == UEMIS_MEM_OK) { - /* if the memory isn's completely full we can try to read more divelog vs. dive details - * UEMIS_MEM_CRITICAL means not enough space for a full round but the dive details - * and the divespots should fit into the UEMIS memory - * The match we do here is to map the object_id to the logfilenr, we do this - * by iterating through the last set of loaded divelogs and then find the corresponding - * dive with the matching logfilenr */ - if (mbuf) { - if (strstr(mbuf, log_file_no_to_find)) { - /* we found the logfilenr that matches our object_id from the divelog we were looking for - * we mark the search successful even if the dive has been deleted. */ - found = true; - if (strstr(mbuf, "deleted{bool{true") == NULL) { - process_raw_buffer(data, deviceidnr, mbuf, &newmax, NULL); - /* remember the last log file number as it is very likely that subsequent dives - * have the same or higher logfile number. - * UEMIS unfortunately deletes dives by deleting the dive details and not the logs. */ -#if UEMIS_DEBUG & 2 - d_time = get_dive_date_c_string(dive->when); - fprintf(debugfile, "Matching divelog id %d from %s with dive details %d\n", dive->dc.diveid, d_time, dive_to_read); -#endif - int divespot_id = uemis_get_divespot_id_by_diveid(dive->dc.diveid); - if (divespot_id >= 0) - get_uemis_divespot(mountpath, divespot_id, dive); - - } else { - /* in this case we found a deleted file, so let's increment */ -#if UEMIS_DEBUG & 2 - d_time = get_dive_date_c_string(dive->when); - fprintf(debugfile, "TRY matching divelog id %d from %s with dive details %d but details are deleted\n", dive->dc.diveid, d_time, dive_to_read); -#endif - deleted_files++; - max_deleted_seen = dive_to_read; - /* mark this log entry as deleted and cleanup later, otherwise we mess up our array */ - dive->downloaded = false; -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "Deleted dive from %s, with id %d from table -- newmax is %s\n", d_time, dive->dc.diveid, newmax); -#endif - } - } else { - uint32_t nr_found = 0; - char *logfilenr = strstr(mbuf, "logfilenr"); - if (logfilenr) { - sscanf(logfilenr, "logfilenr{int{%u", &nr_found); - if (nr_found >= dive->dc.diveid || nr_found == 0) { - found_above = true; - dive_to_read = dive_to_read - 2; - } else { - found_below = true; - } - if (dive_to_read < -1) - dive_to_read = -1; - } - } - } - if (found_above && found_below) - break; - dive_to_read++; - } else { - /* At this point the memory of the UEMIS is full, let's cleanup all divelog files were - * we could not match the details to. */ - do_delete_dives(data->download_table, idx); - return false; - } - } - /* decrement iDiveToRead by the amount of deleted entries found to assure - * we are not missing any valid matches when processing subsequent logs */ - dive_to_read = (dive_to_read - deleted_files > 0 ? dive_to_read - deleted_files : 0); - deleted_files = 0; - return true; -} - -const char *do_uemis_import(device_data_t *data) -{ - const char *mountpath = data->devname; - short force_download = data->force_download; - char *newmax = NULL; - int first, start, end = -2; - uint32_t deviceidnr; - char *deviceid = NULL; - const char *result = NULL; - char *endptr; - bool success, once = true; - int match_dive_and_log = 0; - int uemis_mem_status = UEMIS_MEM_OK; - -#if UEMIS_DEBUG - home = getenv("HOME"); - user = getenv("LOGNAME"); -#endif - uemis_info(translate("gettextFromC", "Initialise communication")); - if (!uemis_init(mountpath)) { - free(reqtxt_path); - return translate("gettextFromC", "Uemis init failed"); - } - - if (!uemis_get_answer(mountpath, "getDeviceId", 0, 1, &result)) - goto bail; - deviceid = strdup(param_buff[0]); - deviceidnr = atoi(deviceid); - - /* param_buff[0] is still valid */ - if (!uemis_get_answer(mountpath, "initSession", 1, 6, &result)) - goto bail; - - uemis_info(translate("gettextFromC", "Start download")); - if (!uemis_get_answer(mountpath, "processSync", 0, 2, &result)) - goto bail; - - /* before starting the long download, check if user pressed cancel */ - if (import_thread_cancelled) - goto bail; - - param_buff[1] = "notempty"; - newmax = uemis_get_divenr(deviceid, force_download); - if (verbose) - fprintf(stderr, "Uemis downloader: start looking at dive nr %s", newmax); - - first = start = atoi(newmax); - dive_to_read = first; - for (;;) { -#if UEMIS_DEBUG & 2 - debug_round++; -#endif -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i inner loop start %d end %d newmax %s\n", start, end, newmax); -#endif - /* start at the last filled download table index */ - match_dive_and_log = data->download_table->nr; - sprintf(newmax, "%d", start); - param_buff[2] = newmax; - param_buff[3] = 0; - success = uemis_get_answer(mountpath, "getDivelogs", 3, 0, &result); - uemis_mem_status = get_memory(data->download_table, UEMIS_CHECK_DETAILS); - /* first, remove any leading garbage... this needs to start with a '{' */ - char *realmbuf = mbuf; - if (mbuf) - realmbuf = strchr(mbuf, '{'); - if (success && realmbuf && uemis_mem_status != UEMIS_MEM_FULL) { -#if UEMIS_DEBUG & 16 - do_dump_buffer_to_file(realmbuf, "Divelogs"); -#endif - /* process the buffer we have assembled */ - if (!process_raw_buffer(data, deviceidnr, realmbuf, &newmax, NULL)) { - /* if no dives were downloaded, mark end appropriately */ - if (end == -2) - end = start - 1; - success = false; - } - if (once) { - char *t = first_object_id_val(realmbuf); - if (t && atoi(t) > start) - start = atoi(t); - free(t); - once = false; - } - /* clean up mbuf */ - endptr = strstr(realmbuf, "{{{"); - if (endptr) - *(endptr + 2) = '\0'; - /* last object_id we parsed */ - sscanf(newmax, "%d", &end); -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i after download and parse start %d end %d newmax %s progress %4.2f\n", start, end, newmax, progress_bar_fraction); -#endif - /* The way this works is that I am reading the current dive from what has been loaded during the getDiveLogs call to the UEMIS. - * As the object_id of the divelog entry and the object_id of the dive details are not necessarily the same, the match needs - * to happen based on the logfilenr. - * What the following part does is to optimize the mapping by using - * dive_to_read = the dive details entry that need to be read using the object_id - * logFileNoToFind = map the logfilenr of the dive details with the object_id = diveid from the get dive logs */ - for (int i = match_dive_and_log; i < data->download_table->nr; i++) { - bool success = get_matching_dive(i, newmax, &uemis_mem_status, data, mountpath, deviceidnr); - if (!success) - break; - if (import_thread_cancelled) - break; - } - - start = end; - - /* Do some memory checking here */ - uemis_mem_status = get_memory(data->download_table, UEMIS_CHECK_LOG); - if (uemis_mem_status != UEMIS_MEM_OK) { -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i out of memory, bailing\n"); -#endif - break; - } - /* if the user clicked cancel, exit gracefully */ - if (import_thread_cancelled) { -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i thread canceled, bailing\n"); -#endif - break; - } - /* if we got an error or got nothing back, stop trying */ - if (!success || !param_buff[3]) { -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i after download nothing found, giving up\n"); -#endif - break; - } -#if UEMIS_DEBUG & 2 - if (debug_round != -1) - if (debug_round-- == 0) { - fprintf(debugfile, "d_u_i debug_round is now 0, bailing\n"); - goto bail; - } -#endif - } else { - /* some of the loading from the UEMIS failed at the divelog level - * if the memory status = full, we can't even load the divespots and/or buddies. - * The loaded block of divelogs is useless and all new loaded divelogs need to - * be deleted from the download_table. - */ - if (uemis_mem_status == UEMIS_MEM_FULL) - do_delete_dives(data->download_table, match_dive_and_log); -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i out of memory, bailing instead of processing\n"); -#endif - break; - } - } - - if (end == -2 && sscanf(newmax, "%d", &end) != 1) - end = start; - -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "Done: read from object_id %d to %d\n", first, end); -#endif - - /* Regardless on where we are with the memory situation, it's time now - * to see if we have to clean some dead bodies from our download table */ - next_table_index = 0; - while (next_table_index < data->download_table->nr) { - if (!data->download_table->dives[next_table_index]->downloaded) - uemis_delete_dive(data, data->download_table->dives[next_table_index]->dc.diveid); - else - next_table_index++; - } - - if (uemis_mem_status != UEMIS_MEM_OK) - result = translate("gettextFromC", ERR_FS_ALMOST_FULL); - -bail: - (void)uemis_get_answer(mountpath, "terminateSync", 0, 3, &result); - if (!strcmp(param_buff[0], "error")) { - if (!strcmp(param_buff[2], "Out of Memory")) - result = translate("gettextFromC", ERR_FS_FULL); - else - result = param_buff[2]; - } - free(deviceid); - free(reqtxt_path); - if (!data->download_table->nr) - result = translate("gettextFromC", ERR_NO_FILES); - return result; -} diff --git a/subsurface-core/uemis.c b/subsurface-core/uemis.c deleted file mode 100644 index 5635d5630..000000000 --- a/subsurface-core/uemis.c +++ /dev/null @@ -1,392 +0,0 @@ -/* - * uemis.c - * - * UEMIS SDA file importer - * AUTHOR: Dirk Hohndel - Copyright 2011 - * - * Licensed under the MIT license. - */ -#include <stdio.h> -#include <string.h> - -#include "gettext.h" - -#include "dive.h" -#include "uemis.h" -#include <libdivecomputer/parser.h> -#include <libdivecomputer/version.h> - -/* - * following code is based on code found in at base64.sourceforge.net/b64.c - * AUTHOR: Bob Trower 08/04/01 - * COPYRIGHT: Copyright (c) Trantor Standard Systems Inc., 2001 - * NOTE: This source code may be used as you wish, subject to - * the MIT license. - */ -/* - * Translation Table to decode (created by Bob Trower) - */ -static const char cd64[] = "|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq"; - -/* - * decodeblock -- decode 4 '6-bit' characters into 3 8-bit binary bytes - */ -static void decodeblock(unsigned char in[4], unsigned char out[3]) -{ - out[0] = (unsigned char)(in[0] << 2 | in[1] >> 4); - out[1] = (unsigned char)(in[1] << 4 | in[2] >> 2); - out[2] = (unsigned char)(((in[2] << 6) & 0xc0) | in[3]); -} - -/* - * decode a base64 encoded stream discarding padding, line breaks and noise - */ -static void decode(uint8_t *inbuf, uint8_t *outbuf, int inbuf_len) -{ - uint8_t in[4], out[3], v; - int i, len, indx_in = 0, indx_out = 0; - - while (indx_in < inbuf_len) { - for (len = 0, i = 0; i < 4 && (indx_in < inbuf_len); i++) { - v = 0; - while ((indx_in < inbuf_len) && v == 0) { - v = inbuf[indx_in++]; - v = ((v < 43 || v > 122) ? 0 : cd64[v - 43]); - if (v) - v = ((v == '$') ? 0 : v - 61); - } - if (indx_in < inbuf_len) { - len++; - if (v) - in[i] = (v - 1); - } else - in[i] = 0; - } - if (len) { - decodeblock(in, out); - for (i = 0; i < len - 1; i++) - outbuf[indx_out++] = out[i]; - } - } -} -/* end code from Bob Trower */ - -/* - * convert the base64 data blog - */ -static int uemis_convert_base64(char *base64, uint8_t **data) -{ - int len, datalen; - - len = strlen(base64); - datalen = (len / 4 + 1) * 3; - if (datalen < 0x123 + 0x25) - /* less than header + 1 sample??? */ - fprintf(stderr, "suspiciously short data block %d\n", datalen); - - *data = malloc(datalen); - if (!*data) { - fprintf(stderr, "Out of memory\n"); - return 0; - } - decode((unsigned char *)base64, *data, len); - - if (memcmp(*data, "Dive\01\00\00", 7)) - fprintf(stderr, "Missing Dive100 header\n"); - - return datalen; -} - -struct uemis_helper { - uint32_t diveid; - int lbs; - int divespot; - int dive_site_uuid; - struct uemis_helper *next; -}; -static struct uemis_helper *uemis_helper = NULL; - -static struct uemis_helper *uemis_get_helper(uint32_t diveid) -{ - struct uemis_helper **php = &uemis_helper; - struct uemis_helper *hp = *php; - - while (hp) { - if (hp->diveid == diveid) - return hp; - if (hp->next) { - hp = hp->next; - continue; - } - php = &hp->next; - break; - } - hp = *php = calloc(1, sizeof(struct uemis_helper)); - hp->diveid = diveid; - hp->next = NULL; - return hp; -} - -static void uemis_weight_unit(int diveid, int lbs) -{ - struct uemis_helper *hp = uemis_get_helper(diveid); - if (hp) - hp->lbs = lbs; -} - -int uemis_get_weight_unit(uint32_t diveid) -{ - struct uemis_helper *hp = uemis_helper; - while (hp) { - if (hp->diveid == diveid) - return hp->lbs; - hp = hp->next; - } - /* odd - we should have found this; default to kg */ - return 0; -} - -void uemis_mark_divelocation(int diveid, int divespot, uint32_t dive_site_uuid) -{ - struct uemis_helper *hp = uemis_get_helper(diveid); - hp->divespot = divespot; - hp->dive_site_uuid = dive_site_uuid; -} - -/* support finding a dive spot based on the diveid */ -int uemis_get_divespot_id_by_diveid(uint32_t diveid) -{ - struct uemis_helper *hp = uemis_helper; - while (hp) { - if (hp->diveid == diveid) - return hp->divespot; - hp = hp->next; - } - return -1; -} - -void uemis_set_divelocation(int divespot, char *text, double longitude, double latitude) -{ - struct uemis_helper *hp = uemis_helper; - while (hp) { - if (hp->divespot == divespot) { - struct dive_site *ds = get_dive_site_by_uuid(hp->dive_site_uuid); - if (ds) { - ds->name = strdup(text); - ds->longitude.udeg = round(longitude * 1000000); - ds->latitude.udeg = round(latitude * 1000000); - } - } - hp = hp->next; - } -} - -/* Create events from the flag bits and other data in the sample; - * These bits basically represent what is displayed on screen at sample time. - * Many of these 'warnings' are way hyper-active and seriously clutter the - * profile plot - so these are disabled by default - * - * we mark all the strings for translation, but we store the untranslated - * strings and only convert them when displaying them on screen - this way - * when we write them to the XML file we'll always have the English strings, - * regardless of locale - */ -static void uemis_event(struct dive *dive, struct divecomputer *dc, struct sample *sample, uemis_sample_t *u_sample) -{ - uint8_t *flags = u_sample->flags; - int stopdepth; - static int lastndl; - - if (flags[1] & 0x01) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Safety stop violation")); - if (flags[1] & 0x08) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Speed alarm")); -#if WANT_CRAZY_WARNINGS - if (flags[1] & 0x06) /* both bits 1 and 2 are a warning */ - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Speed warning")); - if (flags[1] & 0x10) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pOâ‚‚ green warning")); -#endif - if (flags[1] & 0x20) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pOâ‚‚ ascend warning")); - if (flags[1] & 0x40) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pOâ‚‚ ascend alarm")); - /* flags[2] reflects the deco / time bar - * flags[3] reflects more display details on deco and pO2 */ - if (flags[4] & 0x01) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Tank pressure info")); - if (flags[4] & 0x04) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "RGT warning")); - if (flags[4] & 0x08) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "RGT alert")); - if (flags[4] & 0x40) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Tank change suggested")); - if (flags[4] & 0x80) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Depth limit exceeded")); - if (flags[5] & 0x01) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Max deco time warning")); - if (flags[5] & 0x04) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Dive time info")); - if (flags[5] & 0x08) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Dive time alert")); - if (flags[5] & 0x10) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Marker")); - if (flags[6] & 0x02) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "No tank data")); - if (flags[6] & 0x04) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Low battery warning")); - if (flags[6] & 0x08) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Low battery alert")); -/* flags[7] reflects the little on screen icons that remind of previous - * warnings / alerts - not useful for events */ - -#if UEMIS_DEBUG & 32 - int i, j; - for (i = 0; i < 8; i++) { - printf(" %d: ", 29 + i); - for (j = 7; j >= 0; j--) - printf("%c", flags[i] & 1 << j ? '1' : '0'); - } - printf("\n"); -#endif - /* now add deco / NDL - * we don't use events but store this in the sample - that makes much more sense - * for the way we display this information - * What we know about the encoding so far: - * flags[3].bit0 | flags[5].bit1 != 0 ==> in deco - * flags[0].bit7 == 1 ==> Safety Stop - * otherwise NDL */ - stopdepth = rel_mbar_to_depth(u_sample->hold_depth, dive); - if ((flags[3] & 1) | (flags[5] & 2)) { - /* deco */ - sample->in_deco = true; - sample->stopdepth.mm = stopdepth; - sample->stoptime.seconds = u_sample->hold_time * 60; - sample->ndl.seconds = 0; - } else if (flags[0] & 128) { - /* safety stop - distinguished from deco stop by having - * both ndl and stop information */ - sample->in_deco = false; - sample->stopdepth.mm = stopdepth; - sample->stoptime.seconds = u_sample->hold_time * 60; - sample->ndl.seconds = lastndl; - } else { - /* NDL */ - sample->in_deco = false; - lastndl = sample->ndl.seconds = u_sample->hold_time * 60; - sample->stopdepth.mm = 0; - sample->stoptime.seconds = 0; - } -#if UEMIS_DEBUG & 32 - printf("%dm:%ds: p_amb_tol:%d surface:%d holdtime:%d holddepth:%d/%d ---> stopdepth:%d stoptime:%d ndl:%d\n", - sample->time.seconds / 60, sample->time.seconds % 60, u_sample->p_amb_tol, dive->dc.surface_pressure.mbar, - u_sample->hold_time, u_sample->hold_depth, stopdepth, sample->stopdepth.mm, sample->stoptime.seconds, sample->ndl.seconds); -#endif -} - -/* - * parse uemis base64 data blob into struct dive - */ -void uemis_parse_divelog_binary(char *base64, void *datap) -{ - int datalen; - int i; - uint8_t *data; - struct sample *sample = NULL; - uemis_sample_t *u_sample; - struct dive *dive = datap; - struct divecomputer *dc = &dive->dc; - int template, gasoffset; - uint8_t active = 0; - char version[5]; - - datalen = uemis_convert_base64(base64, &data); - dive->dc.airtemp.mkelvin = C_to_mkelvin((*(uint16_t *)(data + 45)) / 10.0); - dive->dc.surface_pressure.mbar = *(uint16_t *)(data + 43); - if (*(uint8_t *)(data + 19)) - dive->dc.salinity = SEAWATER_SALINITY; /* avg grams per 10l sea water */ - else - dive->dc.salinity = FRESHWATER_SALINITY; /* grams per 10l fresh water */ - - /* this will allow us to find the last dive read so far from this computer */ - dc->model = strdup("Uemis Zurich"); - dc->deviceid = *(uint32_t *)(data + 9); - dc->diveid = *(uint16_t *)(data + 7); - /* remember the weight units used in this dive - we may need this later when - * parsing the weight */ - uemis_weight_unit(dc->diveid, *(uint8_t *)(data + 24)); - /* dive template in use: - 0 = air - 1 = nitrox (B) - 2 = nitrox (B+D) - 3 = nitrox (B+T+D) - uemis cylinder data is insane - it stores seven tank settings in a block - and the template tells us which of the four groups of tanks we need to look at - */ - gasoffset = template = *(uint8_t *)(data + 115); - if (template == 3) - gasoffset = 4; - if (template == 0) - template = 1; - for (i = 0; i < template; i++) { - float volume = *(float *)(data + 116 + 25 * (gasoffset + i)) * 1000.0; - /* uemis always assumes a working pressure of 202.6bar (!?!?) - I first thought - * it was 3000psi, but testing against all my dives gets me that strange number. - * Still, that's of course completely bogus and shows they don't get how - * cylinders are named in non-metric parts of the world... - * we store the incorrect working pressure to get the SAC calculations "close" - * but the user will have to correct this manually - */ - dive->cylinder[i].type.size.mliter = rint(volume); - dive->cylinder[i].type.workingpressure.mbar = 202600; - dive->cylinder[i].gasmix.o2.permille = *(uint8_t *)(data + 120 + 25 * (gasoffset + i)) * 10; - dive->cylinder[i].gasmix.he.permille = 0; - } - /* first byte of divelog data is at offset 0x123 */ - i = 0x123; - u_sample = (uemis_sample_t *)(data + i); - while ((i <= datalen) && (data[i] != 0 || data[i + 1] != 0)) { - if (u_sample->active_tank != active) { - if (u_sample->active_tank >= MAX_CYLINDERS) { - fprintf(stderr, "got invalid sensor #%d was #%d\n", u_sample->active_tank, active); - } else { - active = u_sample->active_tank; - add_gas_switch_event(dive, dc, u_sample->dive_time, active); - } - } - sample = prepare_sample(dc); - sample->time.seconds = u_sample->dive_time; - sample->depth.mm = rel_mbar_to_depth(u_sample->water_pressure, dive); - sample->temperature.mkelvin = C_to_mkelvin(u_sample->dive_temperature / 10.0); - sample->sensor = active; - sample->cylinderpressure.mbar = - (u_sample->tank_pressure_high * 256 + u_sample->tank_pressure_low) * 10; - sample->cns = u_sample->cns; - uemis_event(dive, dc, sample, u_sample); - finish_sample(dc); - i += 0x25; - u_sample++; - } - if (sample) - dive->dc.duration.seconds = sample->time.seconds - 1; - - /* get data from the footer */ - char buffer[24]; - - snprintf(version, sizeof(version), "%1u.%02u", data[18], data[17]); - add_extra_data(dc, "FW Version", version); - snprintf(buffer, sizeof(buffer), "%08x", *(uint32_t *)(data + 9)); - add_extra_data(dc, "Serial", buffer); - snprintf(buffer, sizeof(buffer), "%d", *(uint16_t *)(data + i + 35)); - add_extra_data(dc, "main battery after dive", buffer); - snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 24), 60)); - add_extra_data(dc, "no fly time", buffer); - snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 26), 60)); - add_extra_data(dc, "no dive time", buffer); - snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 28), 60)); - add_extra_data(dc, "desat time", buffer); - snprintf(buffer, sizeof(buffer), "%u", *(uint16_t *)(data + i + 30)); - add_extra_data(dc, "allowed altitude", buffer); - - return; -} diff --git a/subsurface-core/uemis.h b/subsurface-core/uemis.h deleted file mode 100644 index 1758b4b32..000000000 --- a/subsurface-core/uemis.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * defines and prototypes for the uemis Zurich SDA file parser - */ - -#ifndef UEMIS_H -#define UEMIS_H - -#include <stdint.h> -#include "dive.h" - -#ifdef __cplusplus -extern "C" { -#endif - -void uemis_parse_divelog_binary(char *base64, void *divep); -int uemis_get_weight_unit(uint32_t diveid); -void uemis_mark_divelocation(int diveid, int divespot, uint32_t dive_site_uuid); -void uemis_set_divelocation(int divespot, char *text, double longitude, double latitude); -int uemis_get_divespot_id_by_diveid(uint32_t diveid); - -typedef struct -{ - uint16_t dive_time; - uint16_t water_pressure; // (in cbar) - uint16_t dive_temperature; // (in dC) - uint8_t ascent_speed; // (units unclear) - uint8_t work_fact; - uint8_t cold_fact; - uint8_t bubble_fact; - uint16_t ascent_time; - uint16_t ascent_time_opt; - uint16_t p_amb_tol; - uint16_t satt; - uint16_t hold_depth; - uint16_t hold_time; - uint8_t active_tank; - // bloody glib, when compiled for Windows, forces the whole program to use - // the Windows packing rules. So to avoid problems on Windows (and since - // only tank_pressure is currently used and that exactly once) I give in and - // make this silly low byte / high byte 8bit entries - uint8_t tank_pressure_low; // (in cbar) - uint8_t tank_pressure_high; - uint8_t consumption_low; // (units unclear) - uint8_t consumption_high; - uint8_t rgt; // (remaining gas time in minutes) - uint8_t cns; - uint8_t flags[8]; -} __attribute((packed)) uemis_sample_t; - -#ifdef __cplusplus -} -#endif - -#endif // UEMIS_H diff --git a/subsurface-core/units.h b/subsurface-core/units.h deleted file mode 100644 index 029bb64fa..000000000 --- a/subsurface-core/units.h +++ /dev/null @@ -1,280 +0,0 @@ -#ifndef UNITS_H -#define UNITS_H - -#include <math.h> -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define O2_IN_AIR 209 // permille -#define N2_IN_AIR 781 -#define O2_DENSITY 1429 // mg/Liter -#define N2_DENSITY 1251 -#define HE_DENSITY 179 -#define SURFACE_PRESSURE 1013 // mbar -#define SURFACE_PRESSURE_STRING "1013" -#define ZERO_C_IN_MKELVIN 273150 // mKelvin - -#ifdef __cplusplus -#define M_OR_FT(_m, _f) ((prefs.units.length == units::METERS) ? ((_m) * 1000) : (feet_to_mm(_f))) -#else -#define M_OR_FT(_m, _f) ((prefs.units.length == METERS) ? ((_m) * 1000) : (feet_to_mm(_f))) -#endif - -/* Salinity is expressed in weight in grams per 10l */ -#define SEAWATER_SALINITY 10300 -#define FRESHWATER_SALINITY 10000 - -#include <stdint.h> -/* - * Some silly typedefs to make our units very explicit. - * - * Also, the units are chosen so that values can be expressible as - * integers, so that we never have FP rounding issues. And they - * are small enough that converting to/from imperial units doesn't - * really matter. - * - * We also strive to make '0' a meaningless number saying "not - * initialized", since many values are things that may not have - * been reported (eg cylinder pressure or temperature from dive - * computers that don't support them). But sometimes -1 is an even - * more explicit way of saying "not there". - * - * Thus "millibar" for pressure, for example, or "millikelvin" for - * temperatures. Doing temperatures in celsius or fahrenheit would - * make for loss of precision when converting from one to the other, - * and using millikelvin is SI-like but also means that a temperature - * of '0' is clearly just a missing temperature or cylinder pressure. - * - * Also strive to use units that can not possibly be mistaken for a - * valid value in a "normal" system without conversion. If the max - * depth of a dive is '20000', you probably didn't convert from mm on - * output, or if the max depth gets reported as "0.2ft" it was either - * a really boring dive, or there was some missing input conversion, - * and a 60-ft dive got recorded as 60mm. - * - * Doing these as "structs containing value" means that we always - * have to explicitly write out those units in order to get at the - * actual value. So there is hopefully little fear of using a value - * in millikelvin as Fahrenheit by mistake. - * - * We don't actually use these all yet, so maybe they'll change, but - * I made a number of types as guidelines. - */ -typedef int64_t timestamp_t; - -typedef struct -{ - uint32_t seconds; // durations up to 68 yrs -} duration_t; - -typedef struct -{ - int32_t seconds; // offsets up to +/- 34 yrs -} offset_t; - -typedef struct -{ - int32_t mm; -} depth_t; // depth to 2000 km - -typedef struct -{ - int32_t mbar; // pressure up to 2000 bar -} pressure_t; - -typedef struct -{ - uint16_t mbar; -} o2pressure_t; // pressure up to 65 bar - -typedef struct -{ - int16_t degrees; -} bearing_t; // compass bearing - -typedef struct -{ - uint32_t mkelvin; // up to 1750 degrees K (temperatures in K are always positive) -} temperature_t; - -typedef struct -{ - int mliter; -} volume_t; - -typedef struct -{ - int permille; -} fraction_t; - -typedef struct -{ - int grams; -} weight_t; - -typedef struct -{ - int udeg; -} degrees_t; - -static inline double udeg_to_radians(int udeg) -{ - return (udeg * M_PI) / (1000000.0 * 180.0); -} - -static inline double grams_to_lbs(int grams) -{ - return grams / 453.6; -} - -static inline int lbs_to_grams(double lbs) -{ - return rint(lbs * 453.6); -} - -static inline double ml_to_cuft(int ml) -{ - return ml / 28316.8466; -} - -static inline double cuft_to_l(double cuft) -{ - return cuft * 28.3168466; -} - -static inline double mm_to_feet(int mm) -{ - return mm * 0.00328084; -} - -static inline double m_to_mile(int m) -{ - return m / 1609.344; -} - -static inline unsigned long feet_to_mm(double feet) -{ - return rint(feet * 304.8); -} - -static inline int to_feet(depth_t depth) -{ - return rint(mm_to_feet(depth.mm)); -} - -static inline double mkelvin_to_C(int mkelvin) -{ - return (mkelvin - ZERO_C_IN_MKELVIN) / 1000.0; -} - -static inline double mkelvin_to_F(int mkelvin) -{ - return mkelvin * 9 / 5000.0 - 459.670; -} - -static inline unsigned long F_to_mkelvin(double f) -{ - return rint((f - 32) * 1000 / 1.8 + ZERO_C_IN_MKELVIN); -} - -static inline unsigned long C_to_mkelvin(double c) -{ - return rint(c * 1000 + ZERO_C_IN_MKELVIN); -} - -static inline double psi_to_bar(double psi) -{ - return psi / 14.5037738; -} - -static inline long psi_to_mbar(double psi) -{ - return rint(psi_to_bar(psi) * 1000); -} - -static inline int to_PSI(pressure_t pressure) -{ - return rint(pressure.mbar * 0.0145037738); -} - -static inline double bar_to_atm(double bar) -{ - return bar / SURFACE_PRESSURE * 1000; -} - -static inline double mbar_to_atm(int mbar) -{ - return (double)mbar / SURFACE_PRESSURE; -} - -static inline int mbar_to_PSI(int mbar) -{ - pressure_t p = { mbar }; - return to_PSI(p); -} - -/* - * We keep our internal data in well-specified units, but - * the input and output may come in some random format. This - * keeps track of those units. - */ -/* turns out in Win32 PASCAL is defined as a calling convention */ -#ifdef WIN32 -#undef PASCAL -#endif -struct units { - enum LENGHT { - METERS, - FEET - } length; - enum VOLUME { - LITER, - CUFT - } volume; - enum PRESSURE { - BAR, - PSI, - PASCAL - } pressure; - enum TEMPERATURE { - CELSIUS, - FAHRENHEIT, - KELVIN - } temperature; - enum WEIGHT { - KG, - LBS - } weight; - enum TIME { - SECONDS, - MINUTES - } vertical_speed_time; -}; - -/* - * We're going to default to SI units for input. Yes, - * technically the SI unit for pressure is Pascal, but - * we default to bar (10^5 pascal), which people - * actually use. Similarly, C instead of Kelvin. - * And kg instead of g. - */ -#define SI_UNITS \ - { \ - .length = METERS, .volume = LITER, .pressure = BAR, .temperature = CELSIUS, .weight = KG, .vertical_speed_time = MINUTES \ - } - -#define IMPERIAL_UNITS \ - { \ - .length = FEET, .volume = CUFT, .pressure = PSI, .temperature = FAHRENHEIT, .weight = LBS, .vertical_speed_time = MINUTES \ - } - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/subsurface-core/version.c b/subsurface-core/version.c deleted file mode 100644 index 764e4d2db..000000000 --- a/subsurface-core/version.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "ssrf-version.h" - -const char *subsurface_git_version(void) -{ - return GIT_VERSION_STRING; -} - -const char *subsurface_canonical_version(void) -{ - return CANONICAL_VERSION_STRING; -} - -#ifdef SUBSURFACE_MOBILE -const char *subsurface_mobile_version(void) -{ - return MOBILE_VERSION_STRING; -} -#endif diff --git a/subsurface-core/version.h b/subsurface-core/version.h deleted file mode 100644 index 0a3204bd9..000000000 --- a/subsurface-core/version.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef VERSION_H -#define VERSION_H - -#ifdef __cplusplus -extern "C" { -#endif - -const char *subsurface_git_version(void); -const char *subsurface_canonical_version(void); - -#ifdef SUBSURFACE_MOBILE -const char *subsurface_mobile_version(void); -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/subsurface-core/webservice.h b/subsurface-core/webservice.h deleted file mode 100644 index 052b8aae7..000000000 --- a/subsurface-core/webservice.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef WEBSERVICE_H -#define WEBSERVICE_H - -#ifdef __cplusplus -extern "C" { -#endif - -//extern void webservice_download_dialog(void); -//extern bool webservice_request_user_xml(const gchar *, gchar **, unsigned int *, unsigned int *); -extern int divelogde_upload(char *fn, char **error); -extern unsigned int download_dialog_parse_response(char *xmldata, unsigned int len); - -enum { - DD_STATUS_OK, - DD_STATUS_ERROR_CONNECT, - DD_STATUS_ERROR_ID, - DD_STATUS_ERROR_PARSE, -}; - - -#ifdef __cplusplus -} -#endif -#endif // WEBSERVICE_H diff --git a/subsurface-core/windows.c b/subsurface-core/windows.c deleted file mode 100644 index 58d3beaad..000000000 --- a/subsurface-core/windows.c +++ /dev/null @@ -1,454 +0,0 @@ -/* windows.c */ -/* implements Windows specific functions */ -#include <io.h> -#include "dive.h" -#include "display.h" -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x500 -#include <windows.h> -#include <shlobj.h> -#include <stdio.h> -#include <fcntl.h> -#include <assert.h> -#include <dirent.h> -#include <zip.h> -#include <lmcons.h> - -const char non_standard_system_divelist_default_font[] = "Calibri"; -const char current_system_divelist_default_font[] = "Segoe UI"; -const char *system_divelist_default_font = non_standard_system_divelist_default_font; -double system_divelist_default_font_size = -1; - -void subsurface_user_info(struct user_info *user) -{ /* Encourage use of at least libgit2-0.20 */ } - -extern bool isWin7Or8(); - -void subsurface_OS_pref_setup(void) -{ - if (isWin7Or8()) - system_divelist_default_font = current_system_divelist_default_font; -} - -bool subsurface_ignore_font(const char *font) -{ - // if this is running on a recent enough version of Windows and the font - // passed in is the pre 4.3 default font, ignore it - if (isWin7Or8() && strcmp(font, non_standard_system_divelist_default_font) == 0) - return true; - return false; -} - -/* this function returns the Win32 Roaming path for the current user as UTF-8. - * it never returns NULL but fallsback to .\ instead! - * the append argument will append a wchar_t string to the end of the path. - */ -static const char *system_default_path_append(const wchar_t *append) -{ - wchar_t wpath[MAX_PATH] = { 0 }; - const char *fname = "system_default_path_append()"; - - /* obtain the user path via SHGetFolderPathW. - * this API is deprecated but still supported on modern Win32. - * fallback to .\ if it fails. - */ - if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, wpath))) { - fprintf(stderr, "%s: cannot obtain path!\n", fname); - wpath[0] = L'.'; - wpath[1] = L'\0'; - } - - wcscat(wpath, L"\\Subsurface"); - if (append) { - wcscat(wpath, L"\\"); - wcscat(wpath, append); - } - - /* attempt to convert the UTF-16 string to UTF-8. - * resize the buffer and fallback to .\Subsurface if it fails. - */ - const int wsz = wcslen(wpath); - const int sz = WideCharToMultiByte(CP_UTF8, 0, wpath, wsz, NULL, 0, NULL, NULL); - char *path = (char *)malloc(sz + 1); - if (!sz) - goto fallback; - if (WideCharToMultiByte(CP_UTF8, 0, wpath, wsz, path, sz, NULL, NULL)) { - path[sz] = '\0'; - return path; - } - -fallback: - fprintf(stderr, "%s: cannot obtain path as UTF-8!\n", fname); - const char *local = ".\\Subsurface"; - const int len = strlen(local) + 1; - path = (char *)realloc(path, len); - memset(path, 0, len); - strcat(path, local); - return path; -} - -/* by passing NULL to system_default_path_append() we obtain the pure path. - * '\' not included at the end. - */ -const char *system_default_directory(void) -{ - static const char *path = NULL; - if (!path) - path = system_default_path_append(NULL); - return path; -} - -/* obtain the Roaming path and append "\\<USERNAME>.xml" to it. - */ -const char *system_default_filename(void) -{ - static wchar_t filename[UNLEN + 5] = { 0 }; - if (!*filename) { - wchar_t username[UNLEN + 1] = { 0 }; - DWORD username_len = UNLEN + 1; - GetUserNameW(username, &username_len); - wcscat(filename, username); - wcscat(filename, L".xml"); - } - static const char *path = NULL; - if (!path) - path = system_default_path_append(filename); - return path; -} - -int enumerate_devices(device_callback_t callback, void *userdata, int dc_type) -{ - int index = -1; - DWORD i; - if (dc_type != DC_TYPE_UEMIS) { - // Open the registry key. - HKEY hKey; - LONG rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_QUERY_VALUE, &hKey); - if (rc != ERROR_SUCCESS) { - return -1; - } - - // Get the number of values. - DWORD count = 0; - rc = RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &count, NULL, NULL, NULL, NULL); - if (rc != ERROR_SUCCESS) { - RegCloseKey(hKey); - return -1; - } - for (i = 0; i < count; ++i) { - // Get the value name, data and type. - char name[512], data[512]; - DWORD name_len = sizeof(name); - DWORD data_len = sizeof(data); - DWORD type = 0; - rc = RegEnumValue(hKey, i, name, &name_len, NULL, &type, (LPBYTE)data, &data_len); - if (rc != ERROR_SUCCESS) { - RegCloseKey(hKey); - return -1; - } - - // Ignore non-string values. - if (type != REG_SZ) - continue; - - // Prevent a possible buffer overflow. - if (data_len >= sizeof(data)) { - RegCloseKey(hKey); - return -1; - } - - // Null terminate the string. - data[data_len] = 0; - - callback(data, userdata); - index++; - if (is_default_dive_computer_device(name)) - index = i; - } - - RegCloseKey(hKey); - } - if (dc_type != DC_TYPE_SERIAL) { - int i; - int count_drives = 0; - const int bufdef = 512; - const char *dlabels[] = {"UEMISSDA", NULL}; - char bufname[bufdef], bufval[bufdef], *p; - DWORD bufname_len; - - /* add drive letters that match labels */ - memset(bufname, 0, bufdef); - bufname_len = bufdef; - if (GetLogicalDriveStringsA(bufname_len, bufname)) { - p = bufname; - - while (*p) { - memset(bufval, 0, bufdef); - if (GetVolumeInformationA(p, bufval, bufdef, NULL, NULL, NULL, NULL, 0)) { - for (i = 0; dlabels[i] != NULL; i++) - if (!strcmp(bufval, dlabels[i])) { - char data[512]; - snprintf(data, sizeof(data), "%s (%s)", p, dlabels[i]); - callback(data, userdata); - if (is_default_dive_computer_device(p)) - index = count_drives; - count_drives++; - } - } - p = &p[strlen(p) + 1]; - } - if (count_drives == 1) /* we found exactly one Uemis "drive" */ - index = 0; /* make it the selected "device" */ - } - } - return index; -} - -/* this function converts a utf-8 string to win32's utf-16 2 byte string. - * the caller function should manage the allocated memory. - */ -static wchar_t *utf8_to_utf16_fl(const char *utf8, char *file, int line) -{ - assert(utf8 != NULL); - assert(file != NULL); - assert(line); - /* estimate buffer size */ - const int sz = strlen(utf8) + 1; - wchar_t *utf16 = (wchar_t *)malloc(sizeof(wchar_t) * sz); - if (!utf16) { - fprintf(stderr, "%s:%d: %s %d.", file, line, "cannot allocate buffer of size", sz); - return NULL; - } - if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, sz)) - return utf16; - fprintf(stderr, "%s:%d: %s", file, line, "cannot convert string."); - free((void *)utf16); - return NULL; -} - -#define utf8_to_utf16(s) utf8_to_utf16_fl(s, __FILE__, __LINE__) - -/* bellow we provide a set of wrappers for some I/O functions to use wchar_t. - * on win32 this solves the issue that we need paths to be utf-16 encoded. - */ -int subsurface_rename(const char *path, const char *newpath) -{ - int ret = -1; - if (!path || !newpath) - return ret; - - wchar_t *wpath = utf8_to_utf16(path); - wchar_t *wnewpath = utf8_to_utf16(newpath); - - if (wpath && wnewpath) - ret = _wrename(wpath, wnewpath); - free((void *)wpath); - free((void *)wnewpath); - return ret; -} - -// if the QDir based rename fails, we try this one -int subsurface_dir_rename(const char *path, const char *newpath) -{ - // check if the folder exists - BOOL exists = FALSE; - DWORD attrib = GetFileAttributes(path); - if (attrib != INVALID_FILE_ATTRIBUTES && attrib & FILE_ATTRIBUTE_DIRECTORY) - exists = TRUE; - if (!exists && verbose) { - fprintf(stderr, "folder not found or path is not a folder: %s\n", path); - return EXIT_FAILURE; - } - - // list of error codes: - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx - DWORD errorCode; - - // if this fails something has already obatained (more) exclusive access to the folder - HANDLE h = CreateFile(path, GENERIC_WRITE, FILE_SHARE_WRITE | - FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); - if (h == INVALID_HANDLE_VALUE) { - errorCode = GetLastError(); - if (verbose) - fprintf(stderr, "cannot obtain exclusive write access for folder: %u\n", (unsigned int)errorCode ); - return EXIT_FAILURE; - } else { - if (verbose) - fprintf(stderr, "exclusive write access obtained...closing handle!"); - CloseHandle(h); - - // attempt to rename - BOOL result = MoveFile(path, newpath); - if (!result) { - errorCode = GetLastError(); - if (verbose) - fprintf(stderr, "rename failed: %u\n", (unsigned int)errorCode); - return EXIT_FAILURE; - } - if (verbose > 1) - fprintf(stderr, "folder rename success: %s ---> %s\n", path, newpath); - } - return EXIT_SUCCESS; -} - -int subsurface_open(const char *path, int oflags, mode_t mode) -{ - int ret = -1; - if (!path) - return ret; - wchar_t *wpath = utf8_to_utf16(path); - if (wpath) - ret = _wopen(wpath, oflags, mode); - free((void *)wpath); - return ret; -} - -FILE *subsurface_fopen(const char *path, const char *mode) -{ - FILE *ret = NULL; - if (!path) - return ret; - wchar_t *wpath = utf8_to_utf16(path); - if (wpath) { - const int len = strlen(mode); - wchar_t wmode[len + 1]; - for (int i = 0; i < len; i++) - wmode[i] = (wchar_t)mode[i]; - wmode[len] = 0; - ret = _wfopen(wpath, wmode); - } - free((void *)wpath); - return ret; -} - -/* here we return a void pointer instead of _WDIR or DIR pointer */ -void *subsurface_opendir(const char *path) -{ - _WDIR *ret = NULL; - if (!path) - return ret; - wchar_t *wpath = utf8_to_utf16(path); - if (wpath) - ret = _wopendir(wpath); - free((void *)wpath); - return (void *)ret; -} - -int subsurface_access(const char *path, int mode) -{ - int ret = -1; - if (!path) - return ret; - wchar_t *wpath = utf8_to_utf16(path); - if (wpath) - ret = _waccess(wpath, mode); - free((void *)wpath); - return ret; -} - -#ifndef O_BINARY -#define O_BINARY 0 -#endif - -struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp) -{ -#if defined(LIBZIP_VERSION_MAJOR) - /* libzip 0.10 has zip_fdopen, let's use it since zip_open doesn't have a - * wchar_t version */ - int fd = subsurface_open(path, O_RDONLY | O_BINARY, 0); - struct zip *ret = zip_fdopen(fd, flags, errorp); - if (!ret) - close(fd); - return ret; -#else - return zip_open(path, flags, errorp); -#endif -} - -int subsurface_zip_close(struct zip *zip) -{ - return zip_close(zip); -} - -/* win32 console */ -static struct { - bool allocated; - UINT cp; - FILE *out, *err; -} console_desc; - -void subsurface_console_init(bool dedicated) -{ - (void)console_desc; - /* if this is a console app already, do nothing */ -#ifndef WIN32_CONSOLE_APP - /* just in case of multiple calls */ - memset((void *)&console_desc, 0, sizeof(console_desc)); - /* the AttachConsole(..) call can be used to determine if the parent process - * is a terminal. if it succeeds, there is no need for a dedicated console - * window and we don't need to call the AllocConsole() function. on the other - * hand if the user has set the 'dedicated' flag to 'true' and if AttachConsole() - * has failed, we create a dedicated console window. - */ - console_desc.allocated = AttachConsole(ATTACH_PARENT_PROCESS); - if (console_desc.allocated) - dedicated = false; - if (!console_desc.allocated && dedicated) - console_desc.allocated = AllocConsole(); - if (!console_desc.allocated) - return; - - console_desc.cp = GetConsoleCP(); - SetConsoleOutputCP(CP_UTF8); /* make the ouput utf8 */ - - /* set some console modes; we don't need to reset these back. - * ENABLE_EXTENDED_FLAGS = 0x0080, ENABLE_QUICK_EDIT_MODE = 0x0040 */ - HANDLE h_in = GetStdHandle(STD_INPUT_HANDLE); - if (h_in) { - SetConsoleMode(h_in, 0x0080 | 0x0040); - CloseHandle(h_in); - } - - /* dedicated only; disable the 'x' button as it will close the main process as well */ - HWND h_cw = GetConsoleWindow(); - if (h_cw && dedicated) { - SetWindowTextA(h_cw, "Subsurface Console"); - HMENU h_menu = GetSystemMenu(h_cw, 0); - if (h_menu) { - EnableMenuItem(h_menu, SC_CLOSE, MF_BYCOMMAND | MF_DISABLED); - DrawMenuBar(h_cw); - } - SetConsoleCtrlHandler(NULL, TRUE); /* disable the CTRL handler */ - } - - /* redirect; on win32, CON is a reserved pipe target, like NUL */ - console_desc.out = freopen("CON", "w", stdout); - console_desc.err = freopen("CON", "w", stderr); - if (!dedicated) - puts(""); /* add an empty line */ -#endif -} - -void subsurface_console_exit(void) -{ -#ifndef WIN32_CONSOLE_APP - if (!console_desc.allocated) - return; - - /* close handles */ - if (console_desc.out) - fclose(console_desc.out); - if (console_desc.err) - fclose(console_desc.err); - - /* reset code page and free */ - SetConsoleOutputCP(console_desc.cp); - FreeConsole(); -#endif -} - -bool subsurface_user_is_root() -{ - /* FIXME: Detect admin rights */ - return (false); -} diff --git a/subsurface-core/windowtitleupdate.cpp b/subsurface-core/windowtitleupdate.cpp deleted file mode 100644 index 963455f1d..000000000 --- a/subsurface-core/windowtitleupdate.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "windowtitleupdate.h" - -WindowTitleUpdate *WindowTitleUpdate::m_instance = NULL; - -WindowTitleUpdate::WindowTitleUpdate(QObject *parent) : QObject(parent) -{ - Q_ASSERT_X(m_instance == NULL, "WindowTitleUpdate", "WindowTitleUpdate recreated!"); - - m_instance = this; -} - -WindowTitleUpdate *WindowTitleUpdate::instance() -{ - return m_instance; -} - -WindowTitleUpdate::~WindowTitleUpdate() -{ - m_instance = NULL; -} - -void WindowTitleUpdate::emitSignal() -{ - emit updateTitle(); -} - -extern "C" void updateWindowTitle() -{ - WindowTitleUpdate *wt = WindowTitleUpdate::instance(); - if (wt) - wt->emitSignal(); -} diff --git a/subsurface-core/windowtitleupdate.h b/subsurface-core/windowtitleupdate.h deleted file mode 100644 index 8650e5868..000000000 --- a/subsurface-core/windowtitleupdate.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef WINDOWTITLEUPDATE_H -#define WINDOWTITLEUPDATE_H - -#include <QObject> - -class WindowTitleUpdate : public QObject -{ - Q_OBJECT -public: - explicit WindowTitleUpdate(QObject *parent = 0); - ~WindowTitleUpdate(); - static WindowTitleUpdate *instance(); - void emitSignal(); -signals: - void updateTitle(); -private: - static WindowTitleUpdate *m_instance; -}; - -#endif // WINDOWTITLEUPDATE_H diff --git a/subsurface-core/worldmap-options.h b/subsurface-core/worldmap-options.h deleted file mode 100644 index 177443563..000000000 --- a/subsurface-core/worldmap-options.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef WORLDMAP_OPTIONS_H -#define WORLDMAP_OPTIONS_H - -const char *map_options = "center: new google.maps.LatLng(0,0),\n\tzoom: 3,\n\tminZoom: 2,\n\tmapTypeId: google.maps.MapTypeId.SATELLITE\n\t"; -const char *css = "\n\thtml { height: 100% }\n\tbody { height: 100%; margin: 0; padding: 0 }\n\t#map-canvas { height: 100% }\n"; - -#endif // WORLDMAP-OPTIONS_H diff --git a/subsurface-core/worldmap-save.c b/subsurface-core/worldmap-save.c deleted file mode 100644 index e7e8bcc30..000000000 --- a/subsurface-core/worldmap-save.c +++ /dev/null @@ -1,117 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include <stdarg.h> -#include <stdlib.h> -#include <string.h> -#include <stdio.h> - -#include "dive.h" -#include "membuffer.h" -#include "save-html.h" -#include "worldmap-save.h" -#include "worldmap-options.h" -#include "gettext.h" - -char *getGoogleApi() -{ - /* google maps api auth*/ - return "https://maps.googleapis.com/maps/api/js?key=AIzaSyDzo9PWsqYDDSddVswg_13rpD9oH_dLuoQ"; -} - -void writeMarkers(struct membuffer *b, const bool selected_only) -{ - int i, dive_no = 0; - struct dive *dive; - char pre[1000], post[1000]; - - for_each_dive (i, dive) { - if (selected_only) { - if (!dive->selected) - continue; - } - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds || !dive_site_has_gps_location(ds)) - continue; - put_degrees(b, ds->latitude, "temp = new google.maps.Marker({position: new google.maps.LatLng(", ""); - put_degrees(b, ds->longitude, ",", ")});\n"); - put_string(b, "markers.push(temp);\ntempinfowindow = new google.maps.InfoWindow({content: '<div id=\"content\">'+'<div id=\"siteNotice\">'+'</div>'+'<div id=\"bodyContent\">"); - snprintf(pre, sizeof(pre), "<p>%s ", translate("gettextFromC", "Date:")); - put_HTML_date(b, dive, pre, "</p>"); - snprintf(pre, sizeof(pre), "<p>%s ", translate("gettextFromC", "Time:")); - put_HTML_time(b, dive, pre, "</p>"); - snprintf(pre, sizeof(pre), "<p>%s ", translate("gettextFromC", "Duration:")); - snprintf(post, sizeof(post), " %s</p>", translate("gettextFromC", "min")); - put_duration(b, dive->duration, pre, post); - put_string(b, "<p> "); - put_HTML_quoted(b, translate("gettextFromC", "Max. depth:")); - put_HTML_depth(b, dive, " ", "</p>"); - put_string(b, "<p> "); - put_HTML_quoted(b, translate("gettextFromC", "Air temp.:")); - put_HTML_airtemp(b, dive, " ", "</p>"); - put_string(b, "<p> "); - put_HTML_quoted(b, translate("gettextFromC", "Water temp.:")); - put_HTML_watertemp(b, dive, " ", "</p>"); - snprintf(pre, sizeof(pre), "<p>%s <b>", translate("gettextFromC", "Location:")); - put_string(b, pre); - put_HTML_quoted(b, get_dive_location(dive)); - put_string(b, "</b></p>"); - snprintf(pre, sizeof(pre), "<p> %s ", translate("gettextFromC", "Notes:")); - put_HTML_notes(b, dive, pre, " </p>"); - put_string(b, "</p>'+'</div>'+'</div>'});\ninfowindows.push(tempinfowindow);\n"); - put_format(b, "google.maps.event.addListener(markers[%d], 'mouseover', function() {\ninfowindows[%d].open(map,markers[%d]);}", dive_no, dive_no, dive_no); - put_format(b, ");google.maps.event.addListener(markers[%d], 'mouseout', function() {\ninfowindows[%d].close();});\n", dive_no, dive_no); - dive_no++; - } -} - -void insert_html_header(struct membuffer *b) -{ - put_string(b, "<!DOCTYPE html>\n<html>\n<head>\n"); - put_string(b, "<meta name=\"viewport\" content=\"initial-scale=1.0, user-scalable=no\" />\n<title>World Map</title>\n"); - put_string(b, "<meta charset=\"UTF-8\">"); -} - -void insert_css(struct membuffer *b) -{ - put_format(b, "<style type=\"text/css\">%s</style>\n", css); -} - -void insert_javascript(struct membuffer *b, const bool selected_only) -{ - put_string(b, "<script type=\"text/javascript\" src=\""); - put_string(b, getGoogleApi()); - put_string(b, "&sensor=false\">\n</script>\n<script type=\"text/javascript\">\nvar map;\n"); - put_format(b, "function initialize() {\nvar mapOptions = {\n\t%s,", map_options); - put_string(b, "rotateControl: false,\n\tstreetViewControl: false,\n\tmapTypeControl: false\n};\n"); - put_string(b, "map = new google.maps.Map(document.getElementById(\"map-canvas\"),mapOptions);\nvar markers = new Array();"); - put_string(b, "\nvar infowindows = new Array();\nvar temp;\nvar tempinfowindow;\n"); - writeMarkers(b, selected_only); - put_string(b, "\nfor(var i=0;i<markers.length;i++)\n\tmarkers[i].setMap(map);\n}\n"); - put_string(b, "google.maps.event.addDomListener(window, 'load', initialize);</script>\n"); -} - -void export(struct membuffer *b, const bool selected_only) -{ - insert_html_header(b); - insert_css(b); - insert_javascript(b, selected_only); - put_string(b, "\t</head>\n<body>\n<div id=\"map-canvas\"></div>\n</body>\n</html>"); -} - -void export_worldmap_HTML(const char *file_name, const bool selected_only) -{ - FILE *f; - - struct membuffer buf = { 0 }; - export(&buf, selected_only); - - f = subsurface_fopen(file_name, "w+"); - if (!f) { - report_error(translate("gettextFromC", "Can't open file %s"), file_name); - } else { - flush_buffer(&buf, f); /*check for writing errors? */ - fclose(f); - } - free_buffer(&buf); -} diff --git a/subsurface-core/worldmap-save.h b/subsurface-core/worldmap-save.h deleted file mode 100644 index 102ea40e5..000000000 --- a/subsurface-core/worldmap-save.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef WORLDMAP_SAVE_H -#define WORLDMAP_SAVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -extern void export_worldmap_HTML(const char *file_name, const bool selected_only); - - -#ifdef __cplusplus -} -#endif - -#endif |