From 4c0156e3d51b389db8eccc3fa3da4b8f248f9b13 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Wed, 2 Sep 2015 20:52:34 -0300 Subject: Move all core-functionality to subsurface-core And adapt a new CMakeLists.txt file for it. On the way I've also found out that we where double-compilling a few files. I've also set the subsurface-core as a include_path but that was just to reduce the noise on this commit, since I plan to remove it from the include path to make it obligatory to specify something like include "subsurface-core/dive.h" for the header files. Since the app is growing quite a bit we ended up having a few different files with almost same name that did similar things, I want to kill that (for instance Dive.h, dive.h, PrintDive.h and such). Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- CMakeLists.txt | 81 +- android.cpp | 205 -- checkcloudconnection.cpp | 95 - checkcloudconnection.h | 22 - cochran.c | 805 ----- cochran.h | 44 - color.h | 67 - configuredivecomputer.cpp | 681 ---- configuredivecomputer.h | 68 - configuredivecomputerthreads.cpp | 1760 ----------- configuredivecomputerthreads.h | 62 - datatrak.c | 695 ----- datatrak.h | 41 - deco.c | 600 ---- deco.h | 19 - device.c | 180 -- device.h | 18 - devicedetails.cpp | 78 - devicedetails.h | 97 - display.h | 63 - dive.c | 3465 -------------------- dive.h | 894 ------ divecomputer.cpp | 228 -- divecomputer.h | 38 - divelist.c | 1141 ------- divelist.h | 62 - divelogexportlogic.cpp | 161 - divelogexportlogic.h | 20 - divesite.c | 337 -- divesite.cpp | 31 - divesite.h | 80 - divesitehelpers.cpp | 208 -- divesitehelpers.h | 18 - equipment.c | 235 -- exif.cpp | 587 ---- exif.h | 147 - file.c | 1066 ------- file.h | 24 - gaspressures.c | 435 --- gaspressures.h | 35 - gettext.h | 10 - gettextfromc.cpp | 27 - gettextfromc.h | 18 - git-access.c | 891 ------ git-access.h | 31 - helpers.h | 52 - libdivecomputer.c | 1083 ------- libdivecomputer.h | 66 - linux.c | 225 -- liquivision.c | 414 --- load-git.c | 1638 ---------- macos.c | 206 -- membuffer.c | 285 -- membuffer.h | 74 - ostctools.c | 193 -- parse-xml.c | 3641 ---------------------- planner.c | 1455 --------- planner.h | 32 - pref.h | 158 - prefs-macros.h | 68 - profile.c | 1463 --------- profile.h | 109 - qt-gui.h | 17 - qt-init.cpp | 48 - qt-models/models.h | 6 +- qt-ui/CMakeLists.txt | 4 + qt-ui/configuredivecomputerdialog.h | 2 +- qt-ui/divelogimportdialog.h | 4 +- qt-ui/graphicsview-common.h | 2 +- qthelper.cpp | 1653 ---------- qthelper.h | 135 - qthelperfromc.h | 21 - qtserialbluetooth.cpp | 415 --- save-git.c | 1235 -------- save-html.c | 533 ---- save-html.h | 30 - save-xml.c | 743 ----- serial_ftdi.c | 664 ---- sha1.c | 300 -- sha1.h | 38 - statistics.c | 379 --- statistics.h | 58 - strndup.h | 21 - strtod.c | 128 - subsurface-core/CMakeLists.txt | 81 + subsurface-core/android.cpp | 205 ++ subsurface-core/checkcloudconnection.cpp | 95 + subsurface-core/checkcloudconnection.h | 22 + subsurface-core/cochran.c | 805 +++++ subsurface-core/cochran.h | 44 + subsurface-core/color.h | 67 + subsurface-core/configuredivecomputer.cpp | 681 ++++ subsurface-core/configuredivecomputer.h | 68 + subsurface-core/configuredivecomputerthreads.cpp | 1760 +++++++++++ subsurface-core/configuredivecomputerthreads.h | 62 + subsurface-core/datatrak.c | 695 +++++ subsurface-core/datatrak.h | 41 + subsurface-core/deco.c | 600 ++++ subsurface-core/deco.h | 19 + subsurface-core/device.c | 180 ++ subsurface-core/device.h | 18 + subsurface-core/devicedetails.cpp | 78 + subsurface-core/devicedetails.h | 97 + subsurface-core/display.h | 63 + subsurface-core/dive.c | 3465 ++++++++++++++++++++ subsurface-core/dive.h | 894 ++++++ subsurface-core/divecomputer.cpp | 228 ++ subsurface-core/divecomputer.h | 38 + subsurface-core/divelist.c | 1141 +++++++ subsurface-core/divelist.h | 62 + subsurface-core/divelogexportlogic.cpp | 161 + subsurface-core/divelogexportlogic.h | 20 + subsurface-core/divesite.c | 337 ++ subsurface-core/divesite.cpp | 31 + subsurface-core/divesite.h | 80 + subsurface-core/divesitehelpers.cpp | 208 ++ subsurface-core/divesitehelpers.h | 18 + subsurface-core/equipment.c | 235 ++ subsurface-core/exif.cpp | 587 ++++ subsurface-core/exif.h | 147 + subsurface-core/file.c | 1066 +++++++ subsurface-core/file.h | 24 + subsurface-core/gaspressures.c | 435 +++ subsurface-core/gaspressures.h | 35 + subsurface-core/gettext.h | 10 + subsurface-core/gettextfromc.cpp | 27 + subsurface-core/gettextfromc.h | 18 + subsurface-core/git-access.c | 891 ++++++ subsurface-core/git-access.h | 31 + subsurface-core/helpers.h | 52 + subsurface-core/libdivecomputer.c | 1083 +++++++ subsurface-core/libdivecomputer.h | 66 + subsurface-core/linux.c | 225 ++ subsurface-core/liquivision.c | 414 +++ subsurface-core/load-git.c | 1638 ++++++++++ subsurface-core/macos.c | 206 ++ subsurface-core/membuffer.c | 285 ++ subsurface-core/membuffer.h | 74 + subsurface-core/ostctools.c | 193 ++ subsurface-core/parse-xml.c | 3641 ++++++++++++++++++++++ subsurface-core/planner.c | 1455 +++++++++ subsurface-core/planner.h | 32 + subsurface-core/pref.h | 158 + subsurface-core/prefs-macros.h | 68 + subsurface-core/profile.c | 1463 +++++++++ subsurface-core/profile.h | 109 + subsurface-core/qt-gui.h | 17 + subsurface-core/qt-init.cpp | 48 + subsurface-core/qthelper.cpp | 1653 ++++++++++ subsurface-core/qthelper.h | 135 + subsurface-core/qthelperfromc.h | 21 + subsurface-core/qtserialbluetooth.cpp | 415 +++ subsurface-core/save-git.c | 1235 ++++++++ subsurface-core/save-html.c | 533 ++++ subsurface-core/save-html.h | 30 + subsurface-core/save-xml.c | 743 +++++ subsurface-core/serial_ftdi.c | 664 ++++ subsurface-core/sha1.c | 300 ++ subsurface-core/sha1.h | 38 + subsurface-core/statistics.c | 379 +++ subsurface-core/statistics.h | 58 + subsurface-core/strndup.h | 21 + subsurface-core/strtod.c | 128 + subsurface-core/subsurfacestartup.c | 319 ++ subsurface-core/subsurfacestartup.h | 26 + subsurface-core/subsurfacesysinfo.cpp | 620 ++++ subsurface-core/subsurfacesysinfo.h | 65 + subsurface-core/taxonomy.c | 48 + subsurface-core/taxonomy.h | 41 + subsurface-core/time.c | 98 + subsurface-core/uemis-downloader.c | 1359 ++++++++ subsurface-core/uemis.c | 392 +++ subsurface-core/uemis.h | 54 + subsurface-core/units.h | 277 ++ subsurface-core/version.c | 16 + subsurface-core/version.h | 16 + subsurface-core/webservice.h | 24 + subsurface-core/windows.c | 448 +++ subsurface-core/windowtitleupdate.cpp | 31 + subsurface-core/windowtitleupdate.h | 20 + subsurface-core/worldmap-options.h | 7 + subsurface-core/worldmap-save.c | 114 + subsurface-core/worldmap-save.h | 15 + subsurfacestartup.c | 319 -- subsurfacestartup.h | 26 - subsurfacesysinfo.cpp | 620 ---- subsurfacesysinfo.h | 65 - taxonomy.c | 48 - taxonomy.h | 41 - time.c | 98 - uemis-downloader.c | 1359 -------- uemis.c | 392 --- uemis.h | 54 - units.h | 277 -- version.c | 16 - version.h | 16 - webservice.h | 24 - windows.c | 448 --- windowtitleupdate.cpp | 31 - windowtitleupdate.h | 20 - worldmap-options.h | 7 - worldmap-save.c | 114 - worldmap-save.h | 15 - 203 files changed, 37461 insertions(+), 37437 deletions(-) delete mode 100644 android.cpp delete mode 100644 checkcloudconnection.cpp delete mode 100644 checkcloudconnection.h delete mode 100644 cochran.c delete mode 100644 cochran.h delete mode 100644 color.h delete mode 100644 configuredivecomputer.cpp delete mode 100644 configuredivecomputer.h delete mode 100644 configuredivecomputerthreads.cpp delete mode 100644 configuredivecomputerthreads.h delete mode 100644 datatrak.c delete mode 100644 datatrak.h delete mode 100644 deco.c delete mode 100644 deco.h delete mode 100644 device.c delete mode 100644 device.h delete mode 100644 devicedetails.cpp delete mode 100644 devicedetails.h delete mode 100644 display.h delete mode 100644 dive.c delete mode 100644 dive.h delete mode 100644 divecomputer.cpp delete mode 100644 divecomputer.h delete mode 100644 divelist.c delete mode 100644 divelist.h delete mode 100644 divelogexportlogic.cpp delete mode 100644 divelogexportlogic.h delete mode 100644 divesite.c delete mode 100644 divesite.cpp delete mode 100644 divesite.h delete mode 100644 divesitehelpers.cpp delete mode 100644 divesitehelpers.h delete mode 100644 equipment.c delete mode 100644 exif.cpp delete mode 100644 exif.h delete mode 100644 file.c delete mode 100644 file.h delete mode 100644 gaspressures.c delete mode 100644 gaspressures.h delete mode 100644 gettext.h delete mode 100644 gettextfromc.cpp delete mode 100644 gettextfromc.h delete mode 100644 git-access.c delete mode 100644 git-access.h delete mode 100644 helpers.h delete mode 100644 libdivecomputer.c delete mode 100644 libdivecomputer.h delete mode 100644 linux.c delete mode 100644 liquivision.c delete mode 100644 load-git.c delete mode 100644 macos.c delete mode 100644 membuffer.c delete mode 100644 membuffer.h delete mode 100644 ostctools.c delete mode 100644 parse-xml.c delete mode 100644 planner.c delete mode 100644 planner.h delete mode 100644 pref.h delete mode 100644 prefs-macros.h delete mode 100644 profile.c delete mode 100644 profile.h delete mode 100644 qt-gui.h delete mode 100644 qt-init.cpp delete mode 100644 qthelper.cpp delete mode 100644 qthelper.h delete mode 100644 qthelperfromc.h delete mode 100644 qtserialbluetooth.cpp delete mode 100644 save-git.c delete mode 100644 save-html.c delete mode 100644 save-html.h delete mode 100644 save-xml.c delete mode 100644 serial_ftdi.c delete mode 100644 sha1.c delete mode 100644 sha1.h delete mode 100644 statistics.c delete mode 100644 statistics.h delete mode 100644 strndup.h delete mode 100644 strtod.c create mode 100644 subsurface-core/CMakeLists.txt create mode 100644 subsurface-core/android.cpp create mode 100644 subsurface-core/checkcloudconnection.cpp create mode 100644 subsurface-core/checkcloudconnection.h create mode 100644 subsurface-core/cochran.c create mode 100644 subsurface-core/cochran.h create mode 100644 subsurface-core/color.h create mode 100644 subsurface-core/configuredivecomputer.cpp create mode 100644 subsurface-core/configuredivecomputer.h create mode 100644 subsurface-core/configuredivecomputerthreads.cpp create mode 100644 subsurface-core/configuredivecomputerthreads.h create mode 100644 subsurface-core/datatrak.c create mode 100644 subsurface-core/datatrak.h create mode 100644 subsurface-core/deco.c create mode 100644 subsurface-core/deco.h create mode 100644 subsurface-core/device.c create mode 100644 subsurface-core/device.h create mode 100644 subsurface-core/devicedetails.cpp create mode 100644 subsurface-core/devicedetails.h create mode 100644 subsurface-core/display.h create mode 100644 subsurface-core/dive.c create mode 100644 subsurface-core/dive.h create mode 100644 subsurface-core/divecomputer.cpp create mode 100644 subsurface-core/divecomputer.h create mode 100644 subsurface-core/divelist.c create mode 100644 subsurface-core/divelist.h create mode 100644 subsurface-core/divelogexportlogic.cpp create mode 100644 subsurface-core/divelogexportlogic.h create mode 100644 subsurface-core/divesite.c create mode 100644 subsurface-core/divesite.cpp create mode 100644 subsurface-core/divesite.h create mode 100644 subsurface-core/divesitehelpers.cpp create mode 100644 subsurface-core/divesitehelpers.h create mode 100644 subsurface-core/equipment.c create mode 100644 subsurface-core/exif.cpp create mode 100644 subsurface-core/exif.h create mode 100644 subsurface-core/file.c create mode 100644 subsurface-core/file.h create mode 100644 subsurface-core/gaspressures.c create mode 100644 subsurface-core/gaspressures.h create mode 100644 subsurface-core/gettext.h create mode 100644 subsurface-core/gettextfromc.cpp create mode 100644 subsurface-core/gettextfromc.h create mode 100644 subsurface-core/git-access.c create mode 100644 subsurface-core/git-access.h create mode 100644 subsurface-core/helpers.h create mode 100644 subsurface-core/libdivecomputer.c create mode 100644 subsurface-core/libdivecomputer.h create mode 100644 subsurface-core/linux.c create mode 100644 subsurface-core/liquivision.c create mode 100644 subsurface-core/load-git.c create mode 100644 subsurface-core/macos.c create mode 100644 subsurface-core/membuffer.c create mode 100644 subsurface-core/membuffer.h create mode 100644 subsurface-core/ostctools.c create mode 100644 subsurface-core/parse-xml.c create mode 100644 subsurface-core/planner.c create mode 100644 subsurface-core/planner.h create mode 100644 subsurface-core/pref.h create mode 100644 subsurface-core/prefs-macros.h create mode 100644 subsurface-core/profile.c create mode 100644 subsurface-core/profile.h create mode 100644 subsurface-core/qt-gui.h create mode 100644 subsurface-core/qt-init.cpp create mode 100644 subsurface-core/qthelper.cpp create mode 100644 subsurface-core/qthelper.h create mode 100644 subsurface-core/qthelperfromc.h create mode 100644 subsurface-core/qtserialbluetooth.cpp create mode 100644 subsurface-core/save-git.c create mode 100644 subsurface-core/save-html.c create mode 100644 subsurface-core/save-html.h create mode 100644 subsurface-core/save-xml.c create mode 100644 subsurface-core/serial_ftdi.c create mode 100644 subsurface-core/sha1.c create mode 100644 subsurface-core/sha1.h create mode 100644 subsurface-core/statistics.c create mode 100644 subsurface-core/statistics.h create mode 100644 subsurface-core/strndup.h create mode 100644 subsurface-core/strtod.c create mode 100644 subsurface-core/subsurfacestartup.c create mode 100644 subsurface-core/subsurfacestartup.h create mode 100644 subsurface-core/subsurfacesysinfo.cpp create mode 100644 subsurface-core/subsurfacesysinfo.h create mode 100644 subsurface-core/taxonomy.c create mode 100644 subsurface-core/taxonomy.h create mode 100644 subsurface-core/time.c create mode 100644 subsurface-core/uemis-downloader.c create mode 100644 subsurface-core/uemis.c create mode 100644 subsurface-core/uemis.h create mode 100644 subsurface-core/units.h create mode 100644 subsurface-core/version.c create mode 100644 subsurface-core/version.h create mode 100644 subsurface-core/webservice.h create mode 100644 subsurface-core/windows.c create mode 100644 subsurface-core/windowtitleupdate.cpp create mode 100644 subsurface-core/windowtitleupdate.h create mode 100644 subsurface-core/worldmap-options.h create mode 100644 subsurface-core/worldmap-save.c create mode 100644 subsurface-core/worldmap-save.h delete mode 100644 subsurfacestartup.c delete mode 100644 subsurfacestartup.h delete mode 100644 subsurfacesysinfo.cpp delete mode 100644 subsurfacesysinfo.h delete mode 100644 taxonomy.c delete mode 100644 taxonomy.h delete mode 100644 time.c delete mode 100644 uemis-downloader.c delete mode 100644 uemis.c delete mode 100644 uemis.h delete mode 100644 units.h delete mode 100644 version.c delete mode 100644 version.h delete mode 100644 webservice.h delete mode 100644 windows.c delete mode 100644 windowtitleupdate.cpp delete mode 100644 windowtitleupdate.h delete mode 100644 worldmap-options.h delete mode 100644 worldmap-save.c delete mode 100644 worldmap-save.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ba6544210..0a14db6de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,12 @@ option(FBSUPPORT "allow posting to Facebook" ON) option(BTSUPPORT "enable support for QtBluetooth (requires Qt5.4 or newer)" ON) option(FTDISUPPORT "enable support for libftdi based serial" OFF) +add_definitions(-DSUBSURFACE_SOURCE="${CMAKE_SOURCE_DIR}") + +if(BTSUPPORT) + add_definitions(-DBT_SUPPORT) +endif() + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${${PROJECT_NAME}_SOURCE_DIR}/cmake/Modules @@ -34,6 +40,7 @@ include_directories(. qt-ui qt-models qt-ui/profile + subsurface-core/ ) # get the version string -- this is only used for Mac Bundle at this point @@ -268,19 +275,16 @@ add_custom_target( set(PLATFORM_SRC unknown_platform.c) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(SUBSURFACE_TARGET subsurface) - set(PLATFORM_SRC linux.c) # in some builds we appear to be missing libz for some strange reason... set(SUBSURFACE_LINK_LIBRARIES ${SUBSURFACE_LINK_LIBRARIES} -lz) endif() if(ANDROID) - set(PLATFORM_SRC android.cpp) set(SUBSURFACE_TARGET subsurface) # To allow us to debug log to logcat set(SUBSURFACE_LINK_LIBRARIES ${SUBSURFACE_LINK_LIBRARIES} -llog) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(SUBSURFACE_TARGET Subsurface) - set(PLATFORM_SRC macos.c) find_library(APP_SERVICES_LIBRARY ApplicationServices) find_library(HID_LIB HidApi) set(SUBSURFACE_LINK_LIBRARIES ${SUBSURFACE_LINK_LIBRARIES} ${HID_LIB}) @@ -299,7 +303,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") set(SUBSURFACE_TARGET subsurface) - set(PLATFORM_SRC windows.c) set(SUBSURFACE_LINK_LIBRARIES ${SUBSURFACE_LINK_LIBRARIES} -lwsock32 -lws2_32) remove_definitions(-DUNICODE) add_definitions(-mwindows -D_WIN32) @@ -307,68 +310,7 @@ endif() # include translations add_subdirectory(translations) - -if(BTSUPPORT) - add_definitions(-DBT_SUPPORT) - set(BT_SRC_FILES qt-ui/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 # some new stuff that is not c code but belongs to divesite. - divelist.c - equipment.c - file.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 - ${BT_CORE_SRC_FILES} - ${SERIAL_FTDI} - ${PLATFORM_SRC} -) -source_group("Subsurface Core" FILES ${SUBSURFACE_CORE_LIB_SRCS}) +add_subdirectory(subsurface-core) if(FBSUPPORT) add_definitions(-DFBSUPPORT) @@ -406,12 +348,9 @@ source_group("Subsurface Models" FILES ${SUBSURFACE_MODELS}) set(SUBSURFACE_APP main.cpp qt-gui.cpp - qthelper.cpp ) source_group("Subsurface App" FILES ${SUBSURFACE_APP}) -add_library(subsurface_corelib STATIC ${SUBSURFACE_CORE_LIB_SRCS} ) -target_link_libraries(subsurface_corelib ${QT_LIBRARIES}) add_library(subsurface_models STATIC ${SUBSURFACE_MODELS_LIB_SRCS}) target_link_libraries(subsurface_models ${QT_LIBRARIES}) @@ -483,7 +422,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows") endif() # build an automated html exporter -add_executable(export-html EXCLUDE_FROM_ALL export-html.cpp qt-init.cpp qthelper.cpp ${SUBSURFACE_RESOURCES}) +add_executable(export-html EXCLUDE_FROM_ALL export-html.cpp ${SUBSURFACE_RESOURCES}) target_link_libraries(export-html subsurface_corelib ${SUBSURFACE_LINK_LIBRARIES}) # QTest based tests @@ -495,7 +434,7 @@ macro(TEST NAME FILE) set_tests_properties(${NAME}_run PROPERTIES DEPENDS ${NAME}_build) endmacro() -add_definitions(-DSUBSURFACE_SOURCE="${CMAKE_SOURCE_DIR}") + add_definitions(-g) if(NOT NO_TESTS) enable_testing() diff --git a/android.cpp b/android.cpp deleted file mode 100644 index 3e14bec02..000000000 --- a/android.cpp +++ /dev/null @@ -1,205 +0,0 @@ -/* implements Android specific functions */ -#include "dive.h" -#include "display.h" -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#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 = 8.0; - -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) -{ - /* Replace this when QtCore/QStandardPaths getExternalStorageDirectory landed */ - QAndroidJniObject externalStorage = QAndroidJniObject::callStaticObjectMethod("android/os/Environment", "getExternalStorageDirectory", "()Ljava/io/File;"); - QAndroidJniObject externalStorageAbsolute = externalStorage.callObjectMethod("getAbsolutePath", "()Ljava/lang/String;"); - QString path = externalStorageAbsolute.toString(); - QAndroidJniEnvironment env; - if (env->ExceptionCheck()) { - // FIXME: Handle exception here. - env->ExceptionClear(); - path = QString("/sdcard"); - } - 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 of all USB devices attached to Android - QAndroidJniObject deviceMap = usbManager.callObjectMethod("getDeviceList", "()Ljava/util/HashMap;"); - jint num_devices = deviceMap.callMethod("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("getVendorId", "()I"); - productid = usbDevice.callMethod("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("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("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/checkcloudconnection.cpp b/checkcloudconnection.cpp deleted file mode 100644 index be2a2fa18..000000000 --- a/checkcloudconnection.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include -#include -#include -#include -#include - -#include "pref.h" -#include "helpers.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() -{ - QTimer timer; - timer.setSingleShot(true); - QEventLoop loop; - QNetworkRequest request; - request.setRawHeader("Accept", "text/plain"); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setUrl(QString(prefs.cloud_base_url) + TEAPOT); - QNetworkAccessManager *mgr = new QNetworkAccessManager(); - reply = mgr->get(request); - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - connect(reply, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); - timer.start(5000); // 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"; - return true; - } - } else { - disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - reply->abort(); - } - 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 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/checkcloudconnection.h b/checkcloudconnection.h deleted file mode 100644 index 58a412797..000000000 --- a/checkcloudconnection.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef CHECKCLOUDCONNECTION_H -#define CHECKCLOUDCONNECTION_H - -#include -#include -#include - -#include "checkcloudconnection.h" - -class CheckCloudConnection : public QObject { - Q_OBJECT -public: - CheckCloudConnection(QObject *parent = 0); - bool checkServer(); -private: - QNetworkReply *reply; -private -slots: - void sslErrors(QList errorList); -}; - -#endif // CHECKCLOUDCONNECTION_H diff --git a/cochran.c b/cochran.c deleted file mode 100644 index 267fe2b4a..000000000 --- a/cochran.c +++ /dev/null @@ -1,805 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "dive.h" -#include "file.h" -#include "units.h" -#include "gettext.h" -#include "cochran.h" -#include "divelist.h" - -#include - -#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 ??? - * - * 0x45415: Time stamp 17 bytes - * 0x45426: Computer configuration page 1, 512 bytes - * 0x45626: Computer configuration page 2, 512 bytes - * - */ -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, 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) -{ - 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/cochran.h b/cochran.h deleted file mode 100644 index 97d4361c8..000000000 --- a/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/color.h b/color.h deleted file mode 100644 index 7938e59a6..000000000 --- a/color.h +++ /dev/null @@ -1,67 +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 - -// 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) - -#endif // COLOR_H diff --git a/configuredivecomputer.cpp b/configuredivecomputer.cpp deleted file mode 100644 index 2457ffe82..000000000 --- a/configuredivecomputer.cpp +++ /dev/null @@ -1,681 +0,0 @@ -#include "configuredivecomputer.h" -#include "libdivecomputer/hw.h" -#include -#include -#include -#include -#include -#include -#include -#include - -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/configuredivecomputer.h b/configuredivecomputer.h deleted file mode 100644 index f14eeeca3..000000000 --- a/configuredivecomputer.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef CONFIGUREDIVECOMPUTER_H -#define CONFIGUREDIVECOMPUTER_H - -#include -#include -#include -#include "libdivecomputer.h" -#include "configuredivecomputerthreads.h" -#include - -#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/configuredivecomputerthreads.cpp b/configuredivecomputerthreads.cpp deleted file mode 100644 index 78edcb37c..000000000 --- a/configuredivecomputerthreads.cpp +++ /dev/null @@ -1,1760 +0,0 @@ -#include "configuredivecomputerthreads.h" -#include "libdivecomputer/hw.h" -#include "libdivecomputer.h" -#include -#include - -#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_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 = 52; - 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); - - //Skip things not supported on the sport, if its a sport. - if (m_deviceDetails->model == "Sport") { - EMIT_PROGRESS(); - EMIT_PROGRESS(); - EMIT_PROGRESS(); - } else { - READ_SETTING(OSTC3_DYNAMIC_ASCEND_RATE, dynamicAscendRate); - READ_SETTING(OSTC3_GRAPHICAL_SPEED_INDICATOR, graphicalSpeedIndicator); - READ_SETTING(OSTC3_ALWAYS_SHOW_PPO2, alwaysShowppO2); - } - -#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(); - - //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 = 51; - - //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); - - //Skip things not supported on the sport, if its a sport. - if (m_deviceDetails->model == "Sport") { - EMIT_PROGRESS(); - EMIT_PROGRESS(); - EMIT_PROGRESS(); - } else { - WRITE_SETTING(OSTC3_DYNAMIC_ASCEND_RATE, dynamicAscendRate); - WRITE_SETTING(OSTC3_GRAPHICAL_SPEED_INDICATOR, graphicalSpeedIndicator); - WRITE_SETTING(OSTC3_ALWAYS_SHOW_PPO2, alwaysShowppO2); - } - -#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(); - - //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_ostc3_device_clock(device, &time); - } - 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 = {}; - dil1.oxygen = data[97]; - dil1.helium = data[98]; - // Byte100-101: - // Gasuent 2 Default (%O2,%He) - // Byte102-103: - // Gasuent 2 Current (%O2,%He) - gas dil2 = {}; - dil2.oxygen = data[101]; - dil2.helium = data[102]; - // Byte104-105: - // Gasuent 3 Default (%O2,%He) - // Byte106-107: - // Gasuent 3 Current (%O2,%He) - gas dil3 = {}; - dil3.oxygen = data[105]; - dil3.helium = data[106]; - // Byte108-109: - // Gasuent 4 Default (%O2,%He) - // Byte110-111: - // Gasuent 4 Current (%O2,%He) - gas dil4 = {}; - dil4.oxygen = data[109]; - dil4.helium = data[110]; - // Byte112-113: - // Gasuent 5 Default (%O2,%He) - // Byte114-115: - // Gasuent 5 Current (%O2,%He) - gas dil5 = {}; - dil5.oxygen = data[113]; - dil5.helium = 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); -#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); -#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) -{ - const dc_event_progress_t *progress = (dc_event_progress_t *) data; - DeviceThread *dt = static_cast(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/configuredivecomputerthreads.h b/configuredivecomputerthreads.h deleted file mode 100644 index 1d7a36f9b..000000000 --- a/configuredivecomputerthreads.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef CONFIGUREDIVECOMPUTERTHREADS_H -#define CONFIGUREDIVECOMPUTERTHREADS_H - -#include -#include -#include -#include "libdivecomputer.h" -#include -#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/datatrak.c b/datatrak.c deleted file mode 100644 index 03fa71a50..000000000 --- a/datatrak.c +++ /dev/null @@ -1,695 +0,0 @@ -#include -#include -#include -#include - -#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 substracts - * (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 necesarily 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/datatrak.h b/datatrak.h deleted file mode 100644 index 3a37e0465..000000000 --- a/datatrak.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef DATATRAK_HEADER_H -#define DATATRAK_HEADER_H - -#include - -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/deco.c b/deco.c deleted file mode 100644 index 86acc0351..000000000 --- a/deco.c +++ /dev/null @@ -1,600 +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 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 -#include -#include "dive.h" -#include -#include - -#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). - unsigned 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 || !in_planner()) { - 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) -{ - 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 && in_planner()) - 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)); -} - -unsigned int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth) -{ - unsigned 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/deco.h b/deco.h deleted file mode 100644 index 08ff93422..000000000 --- a/deco.h +++ /dev/null @@ -1,19 +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; - - -#ifdef __cplusplus -} -#endif - -#endif // DECO_H diff --git a/device.c b/device.c deleted file mode 100644 index c952c84be..000000000 --- a/device.c +++ /dev/null @@ -1,180 +0,0 @@ -#include -#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 recrangular 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) -{ - static struct sample fake[6]; - static struct divecomputer fakedc; - - fakedc = (*dc); - 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)); - 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 absense 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/device.h b/device.h deleted file mode 100644 index 9ff2ce23a..000000000 --- a/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); -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/devicedetails.cpp b/devicedetails.cpp deleted file mode 100644 index 1ac56375d..000000000 --- a/devicedetails.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "devicedetails.h" - -// This can probably be done better by someone with better c++-FU -const struct gas zero_gas = {0}; -const struct setpoint zero_setpoint = {0}; - -DeviceDetails::DeviceDetails(QObject *parent) : - QObject(parent), - data(0), - serialNo(""), - firmwareVersion(""), - customText(""), - model(""), - syncTime(false), - gas1(zero_gas), - gas2(zero_gas), - gas3(zero_gas), - gas4(zero_gas), - gas5(zero_gas), - dil1(zero_gas), - dil2(zero_gas), - dil3(zero_gas), - dil4(zero_gas), - dil5(zero_gas), - sp1(zero_setpoint), - sp2(zero_setpoint), - sp3(zero_setpoint), - sp4(zero_setpoint), - sp5(zero_setpoint), - 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) -{ -} diff --git a/devicedetails.h b/devicedetails.h deleted file mode 100644 index 1ed9914ef..000000000 --- a/devicedetails.h +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef DEVICEDETAILS_H -#define DEVICEDETAILS_H - -#include -#include -#include "libdivecomputer.h" - -struct gas { - unsigned char oxygen; - unsigned char helium; - unsigned char type; - unsigned char depth; -}; - -struct setpoint { - unsigned char sp; - unsigned char depth; -}; - -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; -}; - - -#endif // DEVICEDETAILS_H diff --git a/display.h b/display.h deleted file mode 100644 index 9e3e1d159..000000000 --- a/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/dive.c b/dive.c deleted file mode 100644 index 2ae84ca1e..000000000 --- a/dive.c +++ /dev/null @@ -1,3465 +0,0 @@ -/* dive.c */ -/* maintains the internal dive list structure */ -#include -#include -#include -#include -#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, 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); -} - -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; - 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); - STRUCTURED_LIST_COPY(struct divecomputer, s->dc.next, d->dc.next, copy_dc); - /* this only copied dive computers 2 and up. The first dive computer is part - * of the struct dive, so let's make copies of its samples and events */ - copy_samples(&s->dc, &d->dc); - copy_events(&s->dc, &d->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 - -/* 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) { - int size = sizeof(*ev) + strlen(ev->name) + 1; - struct event *new_ev = malloc(size); - memcpy(new_ev, ev, size); - *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, }; - int lasttime = 0, 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); - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int 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 fixup_pressure(struct dive *dive, struct sample *sample, enum cylinderuse cyl_use) -{ - int pressure, index; - cylinder_t *cyl; - - if (cyl_use != OXYGEN) { - pressure = sample->cylinderpressure.mbar; - index = sample->sensor; - } else { // for the CCR oxygen cylinder: - pressure = sample->o2cylinderpressure.mbar; - index = get_cylinder_idx_by_use(dive, OXYGEN); - } - if (index < 0) - return; - if (!pressure) - return; - - /* - * 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). - */ - if (sample->depth.mm < SURFACE_THRESHOLD) - return; - - /* FIXME! sensor -> cylinder mapping? */ - if (index >= MAX_CYLINDERS) - return; - cyl = dive->cylinder + index; - if (!cyl->sample_start.mbar) - cyl->sample_start.mbar = pressure; - cyl->sample_end.mbar = pressure; -} - -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; - } -} - -/* - * At high pressures air becomes less compressible, and - * does not follow the ideal gas law any more. - * - * This tries to correct for that, becoming the same - * as to_ATM() at lower pressures. - * - * THIS IS A ROUGH APPROXIMATION! The real numbers will - * depend on the exact gas mix and temperature. - */ -double surface_volume_multiplier(pressure_t pressure) -{ - double bar = pressure.mbar / 1000.0; - - if (bar > 200) - bar = 0.00038 * bar * bar + 0.51629 * bar + 81.542; - return bar_to_atm(bar); -} - -int gas_volume(cylinder_t *cyl, pressure_t p) -{ - return cyl->type.size.mliter * surface_volume_multiplier(p); -} - -int wet_volume(double cuft, pressure_t p) -{ - return cuft_to_l(cuft) * 1000 / surface_volume_multiplier(p); -} - -/* - * 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". - */ -static void match_standard_cylinder(cylinder_type_t *type) -{ - double cuft; - int psi, len; - const char *fmt; - char buffer[40], *p; - - /* Do we already have a cylinder description? */ - if (type->description) - return; - - cuft = ml_to_cuft(type->size.mliter); - cuft *= surface_volume_multiplier(type->workingpressure); - 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) { - /* confusing - we don't really start from ml but millicuft !*/ - volume_of_air = cuft_to_l(type->size.mliter); - /* milliliters at 1 atm: "true size" */ - volume = volume_of_air / surface_volume_multiplier(type->workingpressure); - 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) { - 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_dive_dc(struct dive *dive, struct divecomputer *dc) -{ - int i, j; - double depthtime = 0; - int lasttime = 0; - int lastindex = -1; - int maxdepth = dc->maxdepth.mm; - int mintemp = 0; - int lastdepth = 0; - int lasttemp = 0; - int lastpressure = 0, lasto2pressure = 0; - int pressure_delta[MAX_CYLINDERS] = { INT_MAX, }; - int first_cylinder; - - /* 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); - update_min_max_temperatures(dive, dc->watertemp); - - /* make sure we know for which tank the pressure values are intended */ - first_cylinder = explicit_first_cylinder(dive, dc); - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int time = sample->time.seconds; - int depth = sample->depth.mm; - int temp = sample->temperature.mkelvin; - int pressure = sample->cylinderpressure.mbar; - int o2_pressure = sample->o2cylinderpressure.mbar; - int index; - - if (depth < 0) { - depth = interpolate_depth(dc, i, lastdepth, lasttime, time); - sample->depth.mm = depth; - } - - /* if we have an explicit first cylinder */ - if (sample->sensor == 0 && first_cylinder != 0) - sample->sensor = first_cylinder; - - 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 (depth > SURFACE_THRESHOLD) { - if (depth > maxdepth) - maxdepth = depth; - } - - fixup_pressure(dive, sample, OC_GAS); - if (dive->dc.divemode == CCR) - fixup_pressure(dive, sample, OXYGEN); - - 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); - - depthtime += (time - lasttime) * (lastdepth + depth) / 2; - lastdepth = depth; - lasttime = time; - if (sample->cns > dive->maxcns) - dive->maxcns = sample->cns; - } - - /* 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; - } - } - - update_temperature(&dc->watertemp, mintemp); - update_depth(&dc->maxdepth, maxdepth); - if (maxdepth > dive->maxdepth.mm) - dive->maxdepth.mm = maxdepth; - 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 gass 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) { - 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 syncronizes 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) -{ - int i = 0, 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() -{ - int 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 teh 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 migth 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 migth 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_maxdepth = dc->maxdepth.mm; - 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, t, nr; - 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; - } - - // Goind 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; - } -} - -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); -} - -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); - } - } -} - -static void picture_free(struct picture *picture) -{ - if (!picture) - return; - free(picture->filename); - free(picture->hash); - free(picture); -} - -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; - } -} - -/* 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); -} - -/* always acts on the current dive */ -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--; -} - -/* 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, 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/dive.h b/dive.h deleted file mode 100644 index cef1106fd..000000000 --- a/dive.h +++ /dev/null @@ -1,894 +0,0 @@ -#ifndef DIVE_H -#define DIVE_H - -#include -#include -#include -#include -#include -#include -#include -#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 -#include -#include - -#include "sha1.h" -#include "units.h" - -#ifdef __cplusplus -extern "C" { -#else -#include -#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 int wet_volume(double cuft, pressure_t p); - - -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 -}; - -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 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 int explicit_first_cylinder(struct dive *dive, struct divecomputer *dc); -extern int get_depth_at_time(struct divecomputer *dc, 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; - 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; - -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 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 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 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 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 void renumber_dives(int start_nr, bool selected_only); -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, 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); - -#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 unsigned int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth); -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; - unsigned 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/divecomputer.cpp b/divecomputer.cpp deleted file mode 100644 index e4081e1cd..000000000 --- a/divecomputer.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "divecomputer.h" -#include "dive.h" - -#include - -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::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::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 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/divecomputer.h b/divecomputer.h deleted file mode 100644 index 98d12ab8b..000000000 --- a/divecomputer.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef DIVECOMPUTER_H -#define DIVECOMPUTER_H - -#include -#include -#include - -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 dcMap; - QMultiMap dcWorkingMap; -}; - -extern DiveComputerList dcList; - -#endif diff --git a/divelist.c b/divelist.c deleted file mode 100644 index a14fabf88..000000000 --- a/divelist.c +++ /dev/null @@ -1,1141 +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 -#include -#include -#include -#include -#include -#include "gettext.h" -#include -#include -#include - -#include "dive.h" -#include "divelist.h" -#include "display.h" -#include "planner.h" -#include "qthelperfromc.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, j, divenr; - 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++; - 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; -} - -struct dive *merge_two_dives(struct dive *a, struct dive *b) -{ - struct dive *res; - int i, j; - 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; - - 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; - 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(); -} diff --git a/divelist.h b/divelist.h deleted file mode 100644 index 5bae09cff..000000000 --- a/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/divelogexportlogic.cpp b/divelogexportlogic.cpp deleted file mode 100644 index af5157f4a..000000000 --- a/divelogexportlogic.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include -#include -#include -#include -#include -#include -#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/divelogexportlogic.h b/divelogexportlogic.h deleted file mode 100644 index 84f09c362..000000000 --- a/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/divesite.c b/divesite.c deleted file mode 100644 index e9eed2a07..000000000 --- a/divesite.c +++ /dev/null @@ -1,337 +0,0 @@ -/* divesite.c */ -#include "divesite.h" -#include "dive.h" -#include "divelist.h" - -#include - -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/divesite.cpp b/divesite.cpp deleted file mode 100644 index ae102a14b..000000000 --- a/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 = "(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 += ")"; - return locationTag; -} diff --git a/divesite.h b/divesite.h deleted file mode 100644 index f18b2e8e8..000000000 --- a/divesite.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef DIVESITE_H -#define DIVESITE_H - -#include "units.h" -#include "taxonomy.h" -#include - -#ifdef __cplusplus -#include -extern "C" { -#else -#include -#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/divesitehelpers.cpp b/divesitehelpers.cpp deleted file mode 100644 index 3542f96fa..000000000 --- a/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 -#include -#include -#include -#include -#include -#include -#include -#include - -struct GeoLookupInfo { - degrees_t lat; - degrees_t lon; - uint32_t uuid; -}; - -QVector 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/divesitehelpers.h b/divesitehelpers.h deleted file mode 100644 index a08069bc0..000000000 --- a/divesitehelpers.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef DIVESITEHELPERS_H -#define DIVESITEHELPERS_H - -#include "units.h" -#include - -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/equipment.c b/equipment.c deleted file mode 100644 index 47c439735..000000000 --- a/equipment.c +++ /dev/null @@ -1,235 +0,0 @@ -/* equipment.c */ -#include -#include -#include -#include -#include -#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/exif.cpp b/exif.cpp deleted file mode 100644 index 0b1cda2bc..000000000 --- a/exif.cpp +++ /dev/null @@ -1,587 +0,0 @@ -#include -/************************************************************************** - 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 -#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/exif.h b/exif.h deleted file mode 100644 index 0fb3a7d4a..000000000 --- a/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 - -// -// 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/file.c b/file.c deleted file mode 100644 index c4032c1f2..000000000 --- a/file.c +++ /dev/null @@ -1,1066 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "gettext.h" -#include -#include - -#include "dive.h" -#include "file.h" -#include "git-access.h" -#include "qthelperfromc.h" - -/* For SAMPLE_* */ -#include - -/* to check XSLT version number */ -#include - -/* 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 == mem->size) - 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, struct memblock *mem) -{ - 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); - } - 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", 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) -{ - 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(const char *filename, 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, mem); - - /* 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(filename, mem, CSV_DEPTH); - if (!strcasecmp(fmt, "LVD")) - return try_to_open_liquivision(filename, mem); - if (!strcasecmp(fmt, "TMP")) - return try_to_open_csv(filename, mem, CSV_TEMP); - if (!strcasecmp(fmt, "HP1")) - return try_to_open_csv(filename, 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 parse_file(const char *filename) -{ - struct git_repository *git; - const char *branch; - 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 */ - return 0; - - if (git && !git_load_dives(git, branch)) - return 0; - - 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. - */ - - if (verbose >= 2) { - fprintf(stderr, "(echo ''; cat %s;echo '') | 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); - } - - 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/file.h b/file.h deleted file mode 100644 index 855109960..000000000 --- a/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, struct memblock *mem); -#ifdef __cplusplus -} -#endif - -#endif // FILE_H diff --git a/gaspressures.c b/gaspressures.c deleted file mode 100644 index 5f46d6080..000000000 --- a/gaspressures.c +++ /dev/null @@ -1,435 +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, int pressure) -{ // 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; - if (entry->sec >= segment->t_end) { - interpolate.pressure_time += entry->pressure_time; - break; - } - if (entry->sec == segment->t_start) { - interpolate.acc_pressure_time = 0; - interpolate.pressure_time = 0; - if (pressure) - interpolate.start = pressure; - continue; - } - if (i < cur) { - if (pressure) { - interpolate.start = pressure; - interpolate.acc_pressure_time = 0; - interpolate.pressure_time = 0; - } else { - interpolate.acc_pressure_time += entry->pressure_time; - interpolate.pressure_time += entry->pressure_time; - } - continue; - } - if (i == cur) { - interpolate.acc_pressure_time += entry->pressure_time; - interpolate.pressure_time += entry->pressure_time; - continue; - } - interpolate.pressure_time += entry->pressure_time; - if (pressure) { - interpolate.end = pressure; - break; - } - } - 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; - 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; - pr_interpolate_t interpolate; - 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, - 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 .. - interpolate = get_pr_interpolate_data(segment, pi, i, pressure); // Set up an interpolation structure - 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 divecomputer *dc, 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) -{ - int i, cylinderid, cylinderindex = -1; - pr_track_t *track_pr[MAX_CYLINDERS] = { NULL, }; - pr_track_t *current = NULL; - bool missing_pr = false; - - 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, dc, 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/gaspressures.h b/gaspressures.h deleted file mode 100644 index 420c117a2..000000000 --- a/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/gettext.h b/gettext.h deleted file mode 100644 index 43ff023c7..000000000 --- a/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/gettextfromc.cpp b/gettextfromc.cpp deleted file mode 100644 index c579e3c3c..000000000 --- a/gettextfromc.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include - -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 self(new gettextFromC()); - return self.data(); -} - -extern "C" const char *trGettext(const char *text) -{ - return gettextFromC::instance()->trGettext(text); -} diff --git a/gettextfromc.h b/gettextfromc.h deleted file mode 100644 index 53df18365..000000000 --- a/gettextfromc.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef GETTEXTFROMC_H -#define GETTEXTFROMC_H - -#include -#include - -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 translationCache; -}; - -#endif // GETTEXTFROMC_H diff --git a/git-access.c b/git-access.c deleted file mode 100644 index 607789f98..000000000 --- a/git-access.c +++ /dev/null @@ -1,891 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dive.h" -#include "membuffer.h" -#include "strndup.h" -#include "qthelperfromc.h" -#include "git-access.h" -#include "gettext.h" - -/* - * The libgit2 people are incompetent at making libraries. They randomly change - * the interfaces, often just renaming things without any sane way to know which - * version you should check for etc etc. It's a disgrace. - */ -#if !LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR < 22 - #define git_remote_lookup(res, repo, name) git_remote_load(res, repo, name) - #if LIBGIT2_VER_MINOR <= 20 - #define git_remote_fetch(remote, refspecs, signature, reflog) git_remote_fetch(remote) - #else - #define git_remote_fetch(remote, refspecs, signature, reflog) git_remote_fetch(remote, signature, reflog) - #endif -#endif - -#if !USE_LIBGIT23_API && !LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR == 22 - #define git_remote_push(remote,refspecs,opts) git_remote_push(remote,refspecs,opts,NULL,NULL) - #define git_reference_set_target(out,ref,id,log_message) git_reference_set_target(out,ref,id,NULL,log_message) - #define git_reset(repo,target,reset_type,checkout_opts) git_reset(repo,target,reset_type,checkout_opts,NULL,NULL) -#endif - -/* - * api break introduced in libgit2 master after 0.22 - let's guess this is the v0.23 API - */ -#if USE_LIBGIT23_API || (!LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR >= 23) - #define git_branch_create(out, repo, branch_name, target, force, signature, log_message) \ - git_branch_create(out, repo, branch_name, target, force) -#endif - -bool is_subsurface_cloud = false; - -int (*update_progress_cb)(int) = NULL; - -void set_git_update_cb(int(*cb)(int)) -{ - update_progress_cb = cb; -} - -static int update_progress(int percent) -{ - static int last_percent = -10; - int ret = 0; - if (update_progress_cb) - ret = (*update_progress_cb)(percent); - if (verbose && percent - 10 >= last_percent) { - last_percent = percent; - fprintf(stderr, "git progress %d%%\n", percent); - } - return ret; -} - -// the checkout_progress_cb doesn't allow canceling of the operation -static void progress_cb(const char *path, size_t completed_steps, size_t total_steps, void *payload) -{ - int percent = 0; - if (total_steps) - percent = 100 * completed_steps / total_steps; - (void)update_progress(percent); -} - -// this randomly assumes that 80% of the time is spent on the objects and 20% on the deltas -// if the user cancels the dialog this is passed back to libgit2 -static int transfer_progress_cb(const git_transfer_progress *stats, void *payload) -{ - int percent = 0; - if (stats->total_objects) - percent = 80 * stats->received_objects / stats->total_objects; - if (stats->total_deltas) - percent += 20 * stats->indexed_deltas / stats->total_deltas; - return update_progress(percent); -} - -static int push_transfer_progress_cb(unsigned int current, unsigned int total, size_t bytes, void *payload) -{ - int percent = 0; - if (total != 0) - percent = 100 * current / total; - return update_progress(percent); -} - -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) -{ - 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; -} - -#if USE_LIBGIT23_API -int credential_ssh_cb(git_cred **out, - const char *url, - const char *username_from_url, - unsigned int 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) -{ - 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) -{ - 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; -} - -#endif - -static int update_remote(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, enum remote_transport rt) -{ - 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; - -#if USE_LIBGIT23_API - 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; -#endif - 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, git_reference *remote, git_oid *base, const git_oid *local_id, const git_oid *remote_id) -{ - 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); -#ifdef USE_LIBGIT23_API - merge_options.tree_flags = GIT_MERGE_TREE_FIND_RENAMES; -#else - merge_options.flags = GIT_MERGE_TREE_FIND_RENAMES; -#endif - 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) && !git_repository_is_bare(repo)) { - git_object *parent; - git_reference_peel(&parent, local, GIT_OBJ_COMMIT); - if (update_git_checkout(repo, parent, merged_tree)) { - goto write_error; - } - } - if (git_reference_set_target(&local, local, &commit_oid, "Subsurface merge event")) - goto write_error; - set_git_id(&commit_oid); - git_signature_free(author); - - 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; - - if (verbose) - fprintf(stderr, "git storage: try to update\n"); - - 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)) - 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 */ - return try_to_git_merge(repo, local, remote, &base, local_id, remote_id); - -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"); - - 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); -#if USE_LIBGIT23_API - 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; - error = git_remote_push(origin, &refspec, &opts); -#else - error = git_remote_push(origin, &refspec, NULL); -#endif - } 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 (verbose) - fprintf(stderr, "sync with remote %s[%s]\n", remote, branch); - - 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"); - return 0; - } - if (verbose) - fprintf(stderr, "git storage: fetch remote\n"); -#if USE_LIBGIT23_API - 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; - error = git_remote_fetch(origin, NULL, &opts, NULL); -#else - error = git_remote_fetch(origin, NULL, NULL, NULL); -#endif - // 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); - 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) -{ - 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"); - -#if USE_LIBGIT23_API - 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; -#endif - 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) - } 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 storage: accessing %s\n", remote); - } - /* 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); - } - return create_local_repo(localdir, remote, branch, rt); -} - -/* - * 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/git-access.h b/git-access.h deleted file mode 100644 index a2a9ba3ae..000000000 --- a/git-access.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef GITACCESS_H -#define GITACCESS_H - -#include "git2.h" - -#ifdef __cplusplus -extern "C" { -#else -#include -#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 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(*cb)(int)); -char *get_local_dir(const char *remote, const char *branch); -#ifdef __cplusplus -} -#endif -#endif // GITACCESS_H - diff --git a/helpers.h b/helpers.h deleted file mode 100644 index 5f2f2f2c5..000000000 --- a/helpers.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * helpers.h - * - * header file for random helper functions of Subsurface - * - */ -#ifndef HELPERS_H -#define HELPERS_H - -#include -#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, int mbar = 0); -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 parseTemperatureToMkelvin(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(); -QString getDateFormat(); -void selectedDivesGasUsed(QVector > &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/libdivecomputer.c b/libdivecomputer.c deleted file mode 100644 index ca8378379..000000000 --- a/libdivecomputer.c +++ /dev/null @@ -1,1083 +0,0 @@ -#include -#include -#include -#include -#include "gettext.h" -#include "dive.h" -#include "device.h" -#include "divelist.h" -#include "display.h" - -#include "libdivecomputer.h" -#include -#include - - -/* 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, int ngases) -{ - static bool shown_warning = false; - int i, rc; - -#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) - int ntanks = 0; - rc = dc_parser_get_field(parser, DC_FIELD_TANK_COUNT, 0, &ntanks); - if (rc == DC_STATUS_SUCCESS) { - if (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(" ", 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("\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, ...) -{ - 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) -{ - // 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; -} - -static inline int year(int year) -{ - if (year < 70) - return year + 2000; - if (year < 100) - return year + 1900; - return year; -} - -/* - * 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 (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) -{ - 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) -{ - 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) -{ - 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) { -#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) { - 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/libdivecomputer.h b/libdivecomputer.h deleted file mode 100644 index 79817e509..000000000 --- a/libdivecomputer.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef LIBDIVECOMPUTER_H -#define LIBDIVECOMPUTER_H - - -/* libdivecomputer */ -#include -#include -#include - -#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/linux.c b/linux.c deleted file mode 100644 index d4131c7ea..000000000 --- a/linux.c +++ /dev/null @@ -1,225 +0,0 @@ -/* linux.c */ -/* implements Linux specific functions */ -#include "dive.h" -#include "display.h" -#include "membuffer.h" -#include -#include -#include -#include -#include -#include -#include -#include - -// 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 - 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 = { 0 }; - 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 >= 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) -{ - /* NOP */ -} - -void subsurface_console_exit(void) -{ - /* NOP */ -} diff --git a/liquivision.c b/liquivision.c deleted file mode 100644 index 295287c15..000000000 --- a/liquivision.c +++ /dev/null @@ -1,414 +0,0 @@ -#include - -#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) -{ - // 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 = "Xen"; - break; - case 1: - case 2: - dc->model = "Xeo"; - break; - case 4: - dc->model = "Lynx"; - break; - default: - dc->model = "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) -{ - 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/load-git.c b/load-git.c deleted file mode 100644 index 39dab4367..000000000 --- a/load-git.c +++ /dev/null @@ -1,1638 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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) -{ - 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) -{ - 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) -{ struct dive *dive = _dive; dive->divemaster = get_utf8(str); } - -static void parse_dive_buddy(char *line, struct membuffer *str, void *_dive) -{ struct dive *dive = _dive; dive->buddy = get_utf8(str); } - -static void parse_dive_suit(char *line, struct membuffer *str, void *_dive) -{ struct dive *dive = _dive; dive->suit = get_utf8(str); } - -static void parse_dive_notes(char *line, struct membuffer *str, void *_dive) -{ struct dive *dive = _dive; dive->notes = get_utf8(str); } - -static void parse_dive_divesiteid(char *line, struct membuffer *str, void *_dive) -{ 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) -{ - 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) -{ struct dive *dive = _dive; dive->airtemp = get_temperature(line); } - -static void parse_dive_watertemp(char *line, struct membuffer *str, void *_dive) -{ struct dive *dive = _dive; dive->watertemp = get_temperature(line); } - -static void parse_dive_duration(char *line, struct membuffer *str, void *_dive) -{ struct dive *dive = _dive; dive->duration = get_duration(line); } - -static void parse_dive_rating(char *line, struct membuffer *str, void *_dive) -{ struct dive *dive = _dive; dive->rating = get_index(line); } - -static void parse_dive_visibility(char *line, struct membuffer *str, void *_dive) -{ struct dive *dive = _dive; dive->visibility = get_index(line); } - -static void parse_dive_notrip(char *line, struct membuffer *str, void *_dive) -{ struct dive *dive = _dive; dive->tripflag = NO_TRIP; } - -static void parse_site_description(char *line, struct membuffer *str, void *_ds) -{ struct dive_site *ds = _ds; ds->description = strdup(mb_cstring(str)); } - -static void parse_site_name(char *line, struct membuffer *str, void *_ds) -{ struct dive_site *ds = _ds; ds->name = strdup(mb_cstring(str)); } - -static void parse_site_notes(char *line, struct membuffer *str, void *_ds) -{ 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) -{ - 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) -{ struct divecomputer *dc = _dc; dc->airtemp = get_temperature(line); } - -static void parse_dc_date(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; update_date(&dc->when, line); } - -static void parse_dc_deviceid(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->deviceid = get_hex(line); } - -static void parse_dc_diveid(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->diveid = get_hex(line); } - -static void parse_dc_duration(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->duration = get_duration(line); } - -static void parse_dc_dctype(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->divemode = get_dctype(line); } - -static void parse_dc_maxdepth(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->maxdepth = get_depth(line); } - -static void parse_dc_meandepth(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->meandepth = get_depth(line); } - -static void parse_dc_model(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->model = get_utf8(str); } - -static void parse_dc_numberofoxygensensors(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->no_o2sensors = get_index(line); } - -static void parse_dc_surfacepressure(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->surface_pressure = get_pressure(line); } - -static void parse_dc_salinity(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->salinity = get_salinity(line); } - -static void parse_dc_surfacetime(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; dc->surfacetime = get_duration(line); } - -static void parse_dc_time(char *line, struct membuffer *str, void *_dc) -{ struct divecomputer *dc = _dc; update_time(&dc->when, line); } - -static void parse_dc_watertemp(char *line, struct membuffer *str, void *_dc) -{ 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); - 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) -{ dive_trip_t *trip = _trip; update_date(&trip->when, line); } - -static void parse_trip_time(char *line, struct membuffer *str, void *_trip) -{ dive_trip_t *trip = _trip; update_time(&trip->when, line); } - -static void parse_trip_location(char *line, struct membuffer *str, void *_trip) -{ dive_trip_t *trip = _trip; trip->location = get_utf8(str); } - -static void parse_trip_notes(char *line, struct membuffer *str, void *_trip) -{ dive_trip_t *trip = _trip; trip->notes = get_utf8(str); } - -static void parse_settings_autogroup(char *line, struct membuffer *str, void *_unused) -{ set_autogroup(1); } - -static void parse_settings_units(char *line, struct membuffer *str, void *unused) -{ - if (line) - set_informational_units(line); -} - -static void parse_settings_userid(char *line, struct membuffer *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) -{ - 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) -{ } - -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) -{ - 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) -{ - struct picture *pic = _pic; - pic->filename = get_utf8(str); -} - -static void parse_picture_gps(char *line, struct membuffer *str, void *_pic) -{ - 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) -{ - 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) -{ - 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 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)); - return GIT_WALK_OK; -} - -static int picture_directory(const char *root, const char *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, 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) -{ - 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; -} - -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- */ - 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 do_git_load(git_repository *repo, const char *branch) -{ - int ret; - git_object *object; - git_commit *commit; - git_tree *tree; - - if (git_revparse_single(&object, repo, branch)) - return report_error("Unable to look up revision '%s'", branch); - if (git_object_peel((git_object **)&commit, object, GIT_OBJ_COMMIT)) - return report_error("Revision '%s' is not a valid commit", branch); - 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; -} - -/* - * 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/macos.c b/macos.c deleted file mode 100644 index aa2be4b3b..000000000 --- a/macos.c +++ /dev/null @@ -1,206 +0,0 @@ -/* macos.c */ -/* implements Mac OS X specific functions */ -#include -#include -#include -#include "dive.h" -#include "display.h" -#include -#include -#include -#include -#include -#include -#include - -void subsurface_user_info(struct user_info *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) -{ - // 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 >= 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 >= 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) -{ - /* NOP */ -} - -void subsurface_console_exit(void) -{ - /* NOP */ -} diff --git a/membuffer.c b/membuffer.c deleted file mode 100644 index 2889a0cdc..000000000 --- a/membuffer.c +++ /dev/null @@ -1,285 +0,0 @@ -#include -#include -#include -#include - -#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 = "
"; - } - 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/membuffer.h b/membuffer.h deleted file mode 100644 index 434b34c71..000000000 --- a/membuffer.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef MEMBUFFER_H -#define MEMBUFFER_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -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/ostctools.c b/ostctools.c deleted file mode 100644 index 4b4cff241..000000000 --- a/ostctools.c +++ /dev/null @@ -1,193 +0,0 @@ -#include -#include -#include - -#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 necesary 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/parse-xml.c b/parse-xml.c deleted file mode 100644 index 3d86222b9..000000000 --- a/parse-xml.c +++ /dev/null @@ -1,3641 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#define __USE_XOPEN -#include -#include -#include -#include -#include -#include - -#include "gettext.h" - -#include "dive.h" -#include "divelist.h" -#include "device.h" -#include "membuffer.h" - -int verbose, quit; -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) -{ - /* 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 - * - * 32.0 - * - * 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) -{ - 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 (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("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 (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) -{ - 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); - 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, ""); - - if (ret) { - xmlParserCtxtPtr ctx; - char buf[] = ""; - int 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) -{ - 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) -{ - dive_start(); - divedate(starttime, &cur_dive->when); - dive_end(); -} - -extern int dm4_events(void *handle, int columns, char **data, char **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) -{ - 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) -{ - 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) -{ - 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) -{ - int i, 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; inumber = 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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - return 0; -} - -extern int cobalt_location(void *handle, int columns, char **data, char **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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - 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/planner.c b/planner.c deleted file mode 100644 index 22d37165a..000000000 --- a/planner.c +++ /dev/null @@ -1,1455 +0,0 @@ -/* planner.c - * - * code that allows us to plan future dives - * - * (c) Dirk Hohndel 2013 - */ -#include -#include -#include -#include -#include "dive.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) -{ - int 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 unsigned int *sort_stops(int *dstops, int dnr, struct gaschanges *gstops, int gnr) -{ - int i, gi, di; - int total = dnr + gnr; - unsigned 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 buf[1000], *deco; - 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, "%s %s
", - translate("gettextFromC", "Warning:"), temp); - dive->notes = strdup(buffer); - - free((void *)buffer); - free((void *)temp); - return; - } - - len = show_disclaimer ? snprintf(buffer, sz_buffer, "
%s

", 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, "
%s
%s

", - translate("gettextFromC", "Subsurface dive plan"), temp); - - if (!plan_verbatim) { - len += snprintf(buffer + len, sz_buffer - len, "
", - translate("gettextFromC", "depth")); - if (plan_display_duration) - len += snprintf(buffer + len, sz_buffer - len, "", - translate("gettextFromC", "duration")); - if (plan_display_runtime) - len += snprintf(buffer + len, sz_buffer - len, "", - translate("gettextFromC", "runtime")); - len += snprintf(buffer + len, sz_buffer - len, - "", - 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
", 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
", 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 )) { - snprintf(temp, sz_temp, translate("gettextFromC", "%3.0f%s"), depthvalue, depth_unit); - len += snprintf(buffer + len, sz_buffer - len, "", temp); - if (plan_display_duration) { - snprintf(temp, sz_temp, translate("gettextFromC", "%3dmin"), (dp->time - lasttime + 30) / 60); - len += snprintf(buffer + len, sz_buffer - len, "", temp); - } - if (plan_display_runtime) { - snprintf(temp, sz_temp, translate("gettextFromC", "%3dmin"), (dp->time + 30) / 60); - len += snprintf(buffer + len, sz_buffer - len, "", 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, "", gasname(&newgasmix), - temp); - } else { - len += snprintf(buffer + len, sz_buffer - len, "", 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, "", gasname(&gasmix), - temp); - } else { - len += snprintf(buffer + len, sz_buffer - len, "", 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, ""); - } - len += snprintf(buffer + len, sz_buffer - len, ""); - 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
", temp); - } - gaschange_after = false; - gasmix = newgasmix; - } - } - lastprintdepth = newdepth; - lastdepth = dp->depth; - lastsetpoint = dp->setpoint; - lastentered = dp->entered; - } while ((dp = nextdp) != NULL); - len += snprintf(buffer + len, sz_buffer - len, "
%s%s%s%s
%s%s%s%s %s%s%s %s%s 
"); - - 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, "

%s: %i%%", temp, dive->cns); - snprintf(temp, sz_temp, "%s", translate("gettextFromC", "OTU")); - len += snprintf(buffer + len, sz_buffer - len, "
%s: %i
", 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, "

%s
", 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), " — %s %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), " — %s %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
", 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, "%s %s
", - 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, "%s %s
", - translate("gettextFromC", "Warning:"), temp); - - } - } - dp = dp->next; - } - } - snprintf(buffer + len, sz_buffer - len, "
"); - dive->notes = strdup(buffer); - - free((void *)buffer); - free((void *)temp); -} - -int ascent_velocity(int depth, int avg_depth, int 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; - unsigned int stopidx; - int depth; - struct gaschanges *gaschanges = NULL; - int gaschangenr; - int *decostoplevels; - int decostoplevelcount; - unsigned 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/planner.h b/planner.h deleted file mode 100644 index a675989e0..000000000 --- a/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/pref.h b/pref.h deleted file mode 100644 index 84975aaaa..000000000 --- a/pref.h +++ /dev/null @@ -1,158 +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; - -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; - 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; -}; -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/prefs-macros.h b/prefs-macros.h deleted file mode 100644 index fe459d3da..000000000 --- a/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/profile.c b/profile.c deleted file mode 100644 index d39133c21..000000000 --- a/profile.c +++ /dev/null @@ -1,1463 +0,0 @@ -/* profile.c */ -/* creates all the necessary data for drawing the dive profile - */ -#include "gettext.h" -#include -#include -#include - -#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); - -#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 { - /* 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)); - } -} - -/* 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++; -} - -/* 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, unsigned 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, unsigned 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, unsigned 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, ~0u); - - 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, ~0u); -} - -static void check_setpoint_events(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) -{ - int i = 0; - pressure_t setpoint; - - 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, ~0u); -} - - -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; - 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; - - 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 && 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 && ev->time.seconds == lasttime + offset) - ev = ev->next; - } - - /* Add events if they are between plot entries */ - while (ev && 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 && 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; - int duration; - - 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) -{ - int i = 0, last = 0; - struct plot_data *last_entry = NULL; - - for (i = 0; i < pi->nr; i++) - fill_sac(dive, pi, i); -} - -static void populate_secondary_sensor_data(struct divecomputer *dc, struct plot_info *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); -} - -/* 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 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; - 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; - 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 - entry->ceiling = deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), surface_pressure, dive, !prefs.calcceiling3m); - 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 (prefs.calcndltts && !print_mode) { - /* only calculate ndl/tts on every 30 seconds */ - if ((entry->sec - last_ndl_tts_calc_time) < 30) { - 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); - /* Restore "real" deco state for next real time step */ - restore_deco_state(cache_data); - free(cache_data); - } - } -#if DECO_CALC_DEBUG & 1 - dump_tissues(); -#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; - init_decompression(dive); - /* 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 */ - calculate_deco_information(dive, dc, pi, false); /* and ceiling information, using gradient factor values in Preferences) */ - 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/profile.h b/profile.h deleted file mode 100644 index a6dbfcf5f..000000000 --- a/profile.h +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef PROFILE_H -#define PROFILE_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/qt-gui.h b/qt-gui.h deleted file mode 100644 index ca038b145..000000000 --- a/qt-gui.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef QT_GUI_H -#define QT_GUI_H - -#include - -void init_qt_late(); -void init_ui(); - -void run_ui(); -void exit_ui(); - -#if defined(SUBSURFACE_MOBILE) -#include -extern QObject *qqWindowObject; -#endif - -#endif // QT_GUI_H diff --git a/qt-init.cpp b/qt-init.cpp deleted file mode 100644 index b52dfd970..000000000 --- a/qt-init.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include -#include -#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/qt-models/models.h b/qt-models/models.h index c9212195e..f152af469 100644 --- a/qt-models/models.h +++ b/qt-models/models.h @@ -15,9 +15,9 @@ #include "metrics.h" -#include "../dive.h" -#include "../divelist.h" -#include "../divecomputer.h" +#include "subsurface-core/dive.h" +#include "subsurface-core/divelist.h" +#include "subsurface-core/divecomputer.h" #include "cleanertablemodel.h" #include "treemodel.h" diff --git a/qt-ui/CMakeLists.txt b/qt-ui/CMakeLists.txt index 9def39ff3..a860faed5 100644 --- a/qt-ui/CMakeLists.txt +++ b/qt-ui/CMakeLists.txt @@ -4,6 +4,10 @@ qt5_wrap_ui(SUBSURFACE_UI_HDRS ${SUBSURFACE_UI}) qt5_add_resources(SUBSURFACE_RESOURCES subsurface.qrc) source_group("Subsurface Interface Files" FILES ${SUBSURFACE_UI}) +if(BTSUPPORT) + set(BT_SRC_FILES btdeviceselectiondialog.cpp) +endif() + # the interface, in C++ set(SUBSURFACE_INTERFACE updatemanager.cpp diff --git a/qt-ui/configuredivecomputerdialog.h b/qt-ui/configuredivecomputerdialog.h index be76644a9..9ad30ac67 100644 --- a/qt-ui/configuredivecomputerdialog.h +++ b/qt-ui/configuredivecomputerdialog.h @@ -4,7 +4,7 @@ #include #include #include "ui_configuredivecomputerdialog.h" -#include "../libdivecomputer.h" +#include "subsurface-core/libdivecomputer.h" #include "configuredivecomputer.h" #include #include diff --git a/qt-ui/divelogimportdialog.h b/qt-ui/divelogimportdialog.h index 03bb14029..2d12c7cac 100644 --- a/qt-ui/divelogimportdialog.h +++ b/qt-ui/divelogimportdialog.h @@ -9,8 +9,8 @@ #include #include -#include "../dive.h" -#include "../divelist.h" +#include "subsurface-core/dive.h" +#include "subsurface-core/divelist.h" namespace Ui { class DiveLogImportDialog; diff --git a/qt-ui/graphicsview-common.h b/qt-ui/graphicsview-common.h index 3c1cb75a0..2a757b2ae 100644 --- a/qt-ui/graphicsview-common.h +++ b/qt-ui/graphicsview-common.h @@ -1,7 +1,7 @@ #ifndef GRAPHICSVIEW_COMMON_H #define GRAPHICSVIEW_COMMON_H -#include "../color.h" +#include "subsurface-core/color.h" #include #include #include diff --git a/qthelper.cpp b/qthelper.cpp deleted file mode 100644 index a12d25333..000000000 --- a/qthelper.cpp +++ /dev/null @@ -1,1653 +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 -#include -#include "file.h" -#include "prefs-macros.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -const char *existing_filename; -static QString shortDateFormat; -static QString dateFormat; -static QString timeFormat; -static QLocale loc; - -#define translate(_context, arg) trGettext(arg) -static const QString DEGREE_SIGNS("dD" UTF8_DEGREE); - -Dive::Dive() : - m_number(-1), - dive(NULL) -{ -} - -Dive::~Dive() -{ -} - -int Dive::number() const -{ - return m_number; -} - -int Dive::id() const -{ - return m_id; -} - -QString Dive::date() const -{ - return m_date; -} - -timestamp_t Dive::timestamp() const -{ - return m_timestamp; -} - -QString Dive::time() const -{ - return m_time; -} - -QString Dive::location() const -{ - return m_location; -} - -QString Dive::duration() const -{ - return m_duration; -} - -QString Dive::depth() const -{ - return m_depth; -} - -QString Dive::divemaster() const -{ - return m_divemaster; -} - -QString Dive::buddy() const -{ - return m_buddy; -} - -QString Dive::airTemp() const -{ - return m_airTemp; -} - -QString Dive::waterTemp() const -{ - return m_waterTemp; -} - -QString Dive::notes() const -{ - return m_notes; -} - -QString Dive::tags() const -{ - return m_tags; -} - -QString Dive::gas() const -{ - return m_gas; -} - -QString Dive::sac() const -{ - return m_sac; -} - -QString Dive::weight() const -{ - return m_weight; -} - -QString Dive::suit() const -{ - return m_suit; -} - -QString Dive::cylinder() const -{ - return m_cylinder; -} - -QString Dive::trip() const -{ - return m_trip; -} - -int Dive::rating() const -{ - return m_rating; -} - -void Dive::put_divemaster() -{ - if (!dive->divemaster) - m_divemaster = "--"; - else - m_divemaster = dive->divemaster; -} - -void Dive::put_date_time() -{ - QDateTime localTime = QDateTime::fromTime_t(dive->when - gettimezoneoffset(displayed_dive.when)); - localTime.setTimeSpec(Qt::UTC); - m_date = localTime.date().toString(dateFormat); - m_time = localTime.time().toString(timeFormat); -} - -void Dive::put_timestamp() -{ - m_timestamp = dive->when; -} - -void Dive::put_location() -{ - m_location = QString::fromUtf8(get_dive_location(dive)); - if (m_location.isEmpty()) { - m_location = "--"; - } -} - -void Dive::put_depth() -{ - m_depth = get_depth_string(dive->dc.maxdepth.mm, true, true); -} - -void Dive::put_duration() -{ - m_duration = get_dive_duration_string(dive->duration.seconds, QObject::tr("h:"), QObject::tr("min")); -} - -void Dive::put_buddy() -{ - if (!dive->buddy) - m_buddy = "--"; - else - m_buddy = dive->buddy; -} - -void Dive::put_temp() -{ - m_airTemp = get_temperature_string(dive->airtemp, true); - m_waterTemp = get_temperature_string(dive->watertemp, true); - if (m_airTemp.isEmpty()) { - m_airTemp = "--"; - } - if (m_waterTemp.isEmpty()) { - m_waterTemp = "--"; - } -} - -void Dive::put_notes() -{ - if (same_string(dive->dc.model, "planned dive")) { - QTextDocument notes; - notes.setHtml(QString::fromUtf8(dive->notes)); - m_notes = notes.toPlainText(); - } else { - m_notes = QString::fromUtf8(dive->notes); - } - if (m_notes.isEmpty()) { - m_notes = "--"; - } -} - -void Dive::put_tags() -{ - char buffer[256]; - taglist_get_tagstring(dive->tag_list, buffer, 256); - m_tags = QString(buffer); -} - -void Dive::put_gas() -{ - int added = 0; - QString gas, gases; - for (int i = 0; i < MAX_CYLINDERS; i++) { - if (!is_cylinder_used(dive, i)) - continue; - gas = dive->cylinder[i].type.description; - gas += QString(!gas.isEmpty() ? " " : "") + gasname(&dive->cylinder[i].gasmix); - // if has a description and if such gas is not already present - if (!gas.isEmpty() && gases.indexOf(gas) == -1) { - if (added > 0) - gases += QString(" / "); - gases += gas; - added++; - } - } - m_gas = gases; -} - -void Dive::put_sac() -{ - if (dive->sac) { - const char *unit; - int decimal; - double value = get_volume_units(dive->sac, &decimal, &unit); - m_sac = QString::number(value, 'f', decimal).append(unit); - } -} - -void Dive::put_weight() -{ - weight_t tw = { total_weight(dive) }; - m_weight = weight_string(tw.grams); -} - -void Dive::put_suit() -{ - m_suit = QString(dive->suit); -} - -void Dive::put_cylinder() -{ - m_cylinder = QString(dive->cylinder[0].type.description); -} - -void Dive::put_trip() -{ - dive_trip *trip = dive->divetrip; - if (trip) { - m_trip = QString(trip->location); - } -} - -QString weight_string(int weight_in_grams) -{ - QString str; - if (get_units()->weight == units::KG) { - int gr = weight_in_grams % 1000; - int kg = weight_in_grams / 1000; - if (kg >= 20.0) { - str = QString("0"); - } else { - str = QString("%1.%2").arg(kg).arg((unsigned)(gr) / 100); - } - } else { - double lbs = grams_to_lbs(weight_in_grams); - str = QString("%1").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1); - } - return (str); -} - -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 getDivesInTrip(dive_trip_t *trip) -{ - QList 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 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 &a, const QPair &b) -{ - return a.second < b.second; -} - -void selectedDivesGasUsed(QVector > &gasUsedOrdered) -{ - int i, j; - struct dive *d; - QMap gasUsed; - for_each_dive (i, d) { - if (!d->selected) - continue; - volume_t diveGases[MAX_CYLINDERS] = {}; - get_gas_used(d, diveGases); - for (j = 0; j < MAX_CYLINDERS; j++) - if (diveGases[j].mliter) { - QString gasName = gasname(&d->cylinder[j].gasmix); - gasUsed[gasName] += diveGases[j].mliter; - } - } - Q_FOREACH(const QString& gas, gasUsed.keys()) { - gasUsedOrdered.append(qMakePair(gas, gasUsed[gas])); - } - qSort(gasUsedOrdered.begin(), gasUsedOrdered.end(), lessThan); -} - -QString getUserAgent() -{ - QString arch; - // fill in the system data - use ':' as separator - // replace all other ':' with ' ' so that this is easy to parse - QString userAgent = QString("Subsurface:%1:").arg(subsurface_version()); - userAgent.append(SubsurfaceSysInfo::prettyOsName().replace(':', ' ') + ":"); - arch = SubsurfaceSysInfo::buildCpuArchitecture().replace(':', ' '); - userAgent.append(arch); - if (arch == "i386") - userAgent.append("/" + SubsurfaceSysInfo::currentCpuArchitecture()); - userAgent.append(":" + uiLanguage(NULL)); - return userAgent; - -} - -QString uiLanguage(QLocale *callerLoc) -{ - QSettings s; - s.beginGroup("Language"); - - if (!s.value("UseSystemLanguage", true).toBool()) { - loc = QLocale(s.value("UiLanguage", QLocale().uiLanguages().first()).toString()); - } else { - loc = QLocale(QLocale().uiLanguages().first()); - } - - QString uiLang = loc.uiLanguages().first(); - s.endGroup(); - - // there's a stupid Qt bug on MacOS where uiLanguages doesn't give us the country info - if (!uiLang.contains('-') && uiLang != loc.bcp47Name()) { - QLocale loc2(loc.bcp47Name()); - loc = loc2; - uiLang = loc2.uiLanguages().first(); - } - if (callerLoc) - *callerLoc = loc; - - // the short format is fine - // the long format uses long weekday and month names, so replace those with the short ones - // for time we don't want the time zone designator and don't want leading zeroes on the hours - shortDateFormat = loc.dateFormat(QLocale::ShortFormat); - dateFormat = loc.dateFormat(QLocale::LongFormat); - dateFormat.replace("dddd,", "ddd").replace("dddd", "ddd").replace("MMMM", "MMM"); - // special hack for Swedish as our switching from long weekday names to short weekday names - // messes things up there - dateFormat.replace("'en' 'den' d:'e'", " d"); - timeFormat = loc.timeFormat(); - timeFormat.replace("(t)", "").replace(" t", "").replace("t", "").replace("hh", "h").replace("HH", "H").replace("'kl'.", ""); - timeFormat.replace(".ss", "").replace(":ss", "").replace("ss", ""); - return uiLang; -} - -QLocale getLocale() -{ - return loc; -} - -QString getDateFormat() -{ - return dateFormat; -} -void set_filename(const char *filename, bool force) -{ - if (!force && existing_filename) - return; - free((void *)existing_filename); - if (filename) - existing_filename = strdup(filename); - else - existing_filename = NULL; -} - -const QString get_dc_nickname(const char *model, uint32_t deviceid) -{ - const DiveComputerNode *existNode = dcList.getExact(model, deviceid); - - if (existNode && !existNode->nickName.isEmpty()) - return existNode->nickName; - else - return model; -} - -QString get_depth_string(int mm, bool showunit, bool showdecimal) -{ - if (prefs.units.length == units::METERS) { - double meters = mm / 1000.0; - return QString("%1%2").arg(meters, 0, 'f', (showdecimal && meters < 20.0) ? 1 : 0).arg(showunit ? translate("gettextFromC", "m") : ""); - } else { - double feet = mm_to_feet(mm); - return QString("%1%2").arg(feet, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "ft") : ""); - } -} - -QString get_depth_string(depth_t depth, bool showunit, bool showdecimal) -{ - return get_depth_string(depth.mm, showunit, showdecimal); -} - -QString get_depth_unit() -{ - if (prefs.units.length == units::METERS) - return QString("%1").arg(translate("gettextFromC", "m")); - else - return QString("%1").arg(translate("gettextFromC", "ft")); -} - -QString get_weight_string(weight_t weight, bool showunit) -{ - QString str = weight_string(weight.grams); - if (get_units()->weight == units::KG) { - str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "kg") : ""); - } else { - str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "lbs") : ""); - } - return (str); -} - -QString get_weight_unit() -{ - if (prefs.units.weight == units::KG) - return QString("%1").arg(translate("gettextFromC", "kg")); - else - return QString("%1").arg(translate("gettextFromC", "lbs")); -} - -/* these methods retrieve used gas per cylinder */ -static unsigned start_pressure(cylinder_t *cyl) -{ - return cyl->start.mbar ?: cyl->sample_start.mbar; -} - -static unsigned end_pressure(cylinder_t *cyl) -{ - return cyl->end.mbar ?: cyl->sample_end.mbar; -} - -QString get_cylinder_used_gas_string(cylinder_t *cyl, bool showunit) -{ - int decimals; - const char *unit; - double gas_usage; - /* Get the cylinder gas use in mbar */ - gas_usage = start_pressure(cyl) - end_pressure(cyl); - /* Can we turn it into a volume? */ - if (cyl->type.size.mliter) { - gas_usage = bar_to_atm(gas_usage / 1000); - gas_usage *= cyl->type.size.mliter; - gas_usage = get_volume_units(gas_usage, &decimals, &unit); - } else { - gas_usage = get_pressure_units(gas_usage, &unit); - decimals = 0; - } - // translate("gettextFromC","%.*f %s" - return QString("%1 %2").arg(gas_usage, 0, 'f', decimals).arg(showunit ? unit : ""); -} - -QString get_temperature_string(temperature_t temp, bool showunit) -{ - if (temp.mkelvin == 0) { - return ""; //temperature not defined - } else if (prefs.units.temperature == units::CELSIUS) { - double celsius = mkelvin_to_C(temp.mkelvin); - return QString("%1%2%3").arg(celsius, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "C") : ""); - } else { - double fahrenheit = mkelvin_to_F(temp.mkelvin); - return QString("%1%2%3").arg(fahrenheit, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "F") : ""); - } -} - -QString get_temp_unit() -{ - if (prefs.units.temperature == units::CELSIUS) - return QString(UTF8_DEGREE "C"); - else - return QString(UTF8_DEGREE "F"); -} - -QString get_volume_string(volume_t volume, bool showunit, int mbar) -{ - const char *unit; - int decimals; - double value = get_volume_units(volume.mliter, &decimals, &unit); - if (mbar) { - // we are showing a tank size - // fix the weird imperial way of denominating size and provide - // reasonable number of decimals - if (prefs.units.volume == units::CUFT) - value *= bar_to_atm(mbar / 1000.0); - decimals = (value > 20.0) ? 0 : (value > 2.0) ? 1 : 2; - } - return QString("%1%2").arg(value, 0, 'f', decimals).arg(showunit ? unit : ""); -} - -QString get_volume_unit() -{ - const char *unit; - (void) get_volume_units(0, NULL, &unit); - return QString(unit); -} - -QString get_pressure_string(pressure_t pressure, bool showunit) -{ - if (prefs.units.pressure == units::BAR) { - double bar = pressure.mbar / 1000.0; - return QString("%1%2").arg(bar, 0, 'f', 1).arg(showunit ? translate("gettextFromC", "bar") : ""); - } else { - double psi = mbar_to_PSI(pressure.mbar); - return QString("%1%2").arg(psi, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "psi") : ""); - } -} - -QString getSubsurfaceDataPath(QString folderToFind) -{ - QString execdir; - QDir folder; - - // first check if we are running in the build dir, so the path that we - // are looking for is just a subdirectory of the execution path; - // this also works on Windows as there we install the dirs - // under the application path - execdir = QCoreApplication::applicationDirPath(); - folder = QDir(execdir.append(QDir::separator()).append(folderToFind)); - if (folder.exists()) - return folder.absolutePath(); - - // next check for the Linux typical $(prefix)/share/subsurface - execdir = QCoreApplication::applicationDirPath(); - if (execdir.contains("bin")) { - folder = QDir(execdir.replace("bin", "share/subsurface/").append(folderToFind)); - if (folder.exists()) - return folder.absolutePath(); - } - // then look for the usual locations on a Mac - execdir = QCoreApplication::applicationDirPath(); - folder = QDir(execdir.append("/../Resources/share/").append(folderToFind)); - if (folder.exists()) - return folder.absolutePath(); - execdir = QCoreApplication::applicationDirPath(); - folder = QDir(execdir.append("/../Resources/").append(folderToFind)); - if (folder.exists()) - return folder.absolutePath(); - return QString(""); -} - -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 parseTemperatureToMkelvin(const QString &text) -{ - int mkelvin; - QString numOnly = text; - numOnly.replace(",", ".").remove(QRegExp("[^-0-9.]")); - if (numOnly.isEmpty()) - return 0; - double number = numOnly.toDouble(); - switch (prefs.units.temperature) { - case units::CELSIUS: - mkelvin = C_to_mkelvin(number); - break; - case units::FAHRENHEIT: - mkelvin = F_to_mkelvin(number); - break; - default: - mkelvin = 0; - } - return mkelvin; -} - -QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText) -{ - int hrs, mins; - mins = (when + 59) / 60; - hrs = mins / 60; - mins -= hrs * 60; - - QString displayTime; - if (hrs) - displayTime = QString("%1%2%3%4").arg(hrs).arg(hourText).arg(mins, 2, 10, QChar('0')).arg(minutesText); - else - displayTime = QString("%1%2").arg(mins).arg(minutesText); - - return displayTime; -} - -QString get_dive_date_string(timestamp_t when) -{ - QDateTime ts; - ts.setMSecsSinceEpoch(when * 1000L); - return loc.toString(ts.toUTC(), dateFormat + " " + timeFormat); -} - -QString get_short_dive_date_string(timestamp_t when) -{ - QDateTime ts; - ts.setMSecsSinceEpoch(when * 1000L); - return loc.toString(ts.toUTC(), shortDateFormat + " " + timeFormat); -} - -const char *get_dive_date_c_string(timestamp_t when) -{ - QString text = get_dive_date_string(when); - return strdup(text.toUtf8().data()); -} - -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(dateFormat) + 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 hashOf; -QMutex hashOfMutex; -QHash localFilenameOf; - -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; - hashfile.close(); - } -} - -void write_hashes() -{ - QSaveFile hashfile(hashfile_name()); - if (hashfile.open(QIODevice::WriteOnly)) { - QDataStream stream(&hashfile); - stream << localFilenameOf; - stream << hashOf; - 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)]; -} - -void updateHash(struct picture *picture) { - QByteArray hash = hashFile(fileFromHash(picture->hash)); - QMutexLocker locker(&hashOfMutex); - hashOf[QString(picture->filename)] = hash; - char *old = picture->hash; - picture->hash = strdup(hash.toHex()); - free(old); -} - -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); -} - -extern "C" void cache_picture(struct picture *picture) -{ - QString filename = picture->filename; - if (!hashOf.contains(filename)) - QtConcurrent::run(hashPicture, picture); -} - -void learnImages(const QDir dir, int max_recursions, bool recursed) -{ - QDir current(dir); - QStringList filters, files; - - if (max_recursions) { - foreach (QString dirname, dir.entryList(QStringList(), QDir::NoDotAndDotDot | QDir::Dirs)) { - learnImages(QDir(dir.filePath(dirname)), max_recursions - 1, true); - } - } - - foreach (QString format, QImageReader::supportedImageFormats()) { - filters.append(QString("*.").append(format)); - } - - foreach (QString file, dir.entryList(filters, QDir::Files)) { - files.append(dir.absoluteFilePath(file)); - } - - QtConcurrent::blockingMap(files, hashFile); -} - -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; - - 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("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(); - 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); - GET_BOOL("save_password_local", save_password_local); - 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); - - // 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); - - // 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(); - -} - -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"); -} diff --git a/qthelper.h b/qthelper.h deleted file mode 100644 index a2b7b6c39..000000000 --- a/qthelper.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef QTHELPER_H -#define QTHELPER_H - -#include -#include -#include -#include "dive.h" -#include "divelist.h" -#include -#include - -class Dive { -private: - int m_number; - int m_id; - int m_rating; - QString m_date; - timestamp_t m_timestamp; - QString m_time; - QString m_location; - QString m_duration; - QString m_depth; - QString m_divemaster; - QString m_buddy; - QString m_airTemp; - QString m_waterTemp; - QString m_notes; - QString m_tags; - QString m_gas; - QString m_sac; - QString m_weight; - QString m_suit; - QString m_cylinder; - QString m_trip; - struct dive *dive; - void put_date_time(); - void put_timestamp(); - void put_location(); - void put_duration(); - void put_depth(); - void put_divemaster(); - void put_buddy(); - void put_temp(); - void put_notes(); - void put_tags(); - void put_gas(); - void put_sac(); - void put_weight(); - void put_suit(); - void put_cylinder(); - void put_trip(); - -public: - Dive(struct dive *dive) - : dive(dive) - { - m_number = dive->number; - m_id = dive->id; - m_rating = dive->rating; - put_date_time(); - put_location(); - put_duration(); - put_depth(); - put_divemaster(); - put_buddy(); - put_temp(); - put_notes(); - put_tags(); - put_gas(); - put_sac(); - put_timestamp(); - put_weight(); - put_suit(); - put_cylinder(); - put_trip(); - } - Dive(); - ~Dive(); - int number() const; - int id() const; - int rating() const; - QString date() const; - timestamp_t timestamp() const; - QString time() const; - QString location() const; - QString duration() 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 weight() const; - QString suit() const; - QString cylinder() const; - QString trip() const; -}; - -// 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 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, bool recursed); -void add_hash(const QString filename, QByteArray hash); -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); - -#endif // QTHELPER_H diff --git a/qthelperfromc.h b/qthelperfromc.h deleted file mode 100644 index d2e80144c..000000000 --- a/qthelperfromc.h +++ /dev/null @@ -1,21 +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(); - -#endif // QTHELPERFROMC_H diff --git a/qtserialbluetooth.cpp b/qtserialbluetooth.cpp deleted file mode 100644 index 025ab8c34..000000000 --- a/qtserialbluetooth.cpp +++ /dev/null @@ -1,415 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -#include - -#if defined(SSRF_CUSTOM_SERIAL) - -#if defined(Q_OS_WIN) - #include - #include - #include -#endif - -#include - -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) -{ - 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/save-git.c b/save-git.c deleted file mode 100644 index 69ad0726d..000000000 --- a/save-git.c +++ /dev/null @@ -1,1235 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dive.h" -#include "divelist.h" -#include "device.h" -#include "membuffer.h" -#include "git-access.h" -#include "version.h" -#include "qthelperfromc.h" - -/* - * handle libgit2 revision 0.20 and earlier - */ -#if !LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR <= 20 && !defined(USE_LIBGIT21_API) - #define GIT_CHECKOUT_OPTIONS_INIT GIT_CHECKOUT_OPTS_INIT - #define git_checkout_options git_checkout_opts - #define git_branch_create(out,repo,branch_name,target,force,sig,msg) \ - git_branch_create(out,repo,branch_name,target,force) - #define git_reference_set_target(out,ref,target,signature,log_message) \ - git_reference_set_target(out,ref,target) -#endif -/* - * api break in libgit2 revision 0.22 - */ -#if !LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR < 22 - #define git_treebuilder_new(out, repo, source) git_treebuilder_create(out, source) -#else - #define git_treebuilder_write(id, repo, bld) git_treebuilder_write(id, bld) -#endif -/* - * api break introduced in libgit2 master after 0.22 - let's guess this is the v0.23 API - */ -#if USE_LIBGIT23_API || (!LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR >= 23) - #define git_branch_create(out, repo, branch_name, target, force, signature, log_message) \ - git_branch_create(out, repo, branch_name, target, force) - #define git_reference_set_target(out, ref, id, author, log_message) \ - git_reference_set_target(out, ref, id, log_message) -#endif - -#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; -} - -/* - * 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 file at filepath to the git repo with given filename */ -static int blob_insert_fromdisk(git_repository *repo, struct dir *tree, const char *filepath, const char *filename) -{ - int ret; - git_oid blob_id; - - ret = git_blob_create_fromdisk(&blob_id, repo, filepath); - if (ret) - return ret; - return tree_insert(tree->files, filename, 1, &blob_id, GIT_FILEMODE_BLOB); -} - -/* - * 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 (!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); - } - 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) -{ - struct divecomputer *dc; - struct membuffer buf = { 0 }, name = { 0 }; - struct dir *subdir; - int ret, nr; - - /* Create dive directory */ - create_dive_name(dive, &name, tm); - 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) -{ - 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); - } - - 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) -{ - 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 */ - for_each_dive(i, dive) { - struct tm tm; - struct dir *tree; - - 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); - continue; - } - - save_one_dive(repo, tree, dive, &tm); - } - 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) -{ - 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_version()); -} - -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, author, "Create branch")) - 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, author, "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, repo, 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; - - if (verbose) - fprintf(stderr, "git storage: do git save\n"); - - /* 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)) - return -1; - - 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/save-html.c b/save-html.c deleted file mode 100644 index 64ce94f66..000000000 --- a/save-html.c +++ /dev/null @@ -1,533 +0,0 @@ -#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_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/save-html.h b/save-html.h deleted file mode 100644 index 20743e90a..000000000 --- a/save-html.h +++ /dev/null @@ -1,30 +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_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/save-xml.c b/save-xml.c deleted file mode 100644 index 166885861..000000000 --- a/save-xml.c +++ /dev/null @@ -1,743 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#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, " 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, " 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, " 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_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, " salinity, " salinity='", " g/l'"); - put_string(b, " />\n"); -} - -static void save_overview(struct membuffer *b, struct dive *dive) -{ - show_utf8(b, dive->divemaster, " ", "\n", 0); - show_utf8(b, dive->buddy, " ", "\n", 0); - show_utf8(b, dive->notes, " ", "\n", 0); - show_utf8(b, dive->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, " 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, " \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, " 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, " 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, " 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, " 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, " ", " min\n"); - save_extra_data(b, dc->extra_data); - save_events(b, dc->events); - save_samples(b, dc->samples, dc->sample); - - put_format(b, " \n"); -} - -static void save_picture(struct membuffer *b, struct picture *pic) -{ - put_string(b, " 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, "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, "\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, "when); - show_utf8(b, trip->location, " location=\'", "\'", 1); - put_format(b, ">\n"); - show_utf8(b, trip->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, "\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, "\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, "\n\n", DATAFORMAT_VERSION); - - if (prefs.save_userid_local) - put_format(b, " %30s\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, " \n"); - put_format(b, "\n"); - - /* save the dive sites - to make the output consistent let's sort the table, first */ - dive_site_table_sort(); - put_format(b, "\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, "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, " ", " \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, " category); - put_format(b, " origin='%d'", t->origin); - show_utf8(b, t->value, " value='", "'/>\n", 1); - } - } - } - put_format(b, "\n"); - } - put_format(b, "\n\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, "\n\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); - - try_to_backup(filename); - - save_dives_buffer(&buf, select_only); - - 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/serial_ftdi.c b/serial_ftdi.c deleted file mode 100644 index cbac026cf..000000000 --- a/serial_ftdi.c +++ /dev/null @@ -1,664 +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 // malloc, free -#include // strerror -#include // errno -#include // gettimeofday -#include // nanosleep -#include - -#include -#include - -#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 -#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 - -/* 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)) { - 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/sha1.c b/sha1.c deleted file mode 100644 index acf8c5d9f..000000000 --- a/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 -#include -#ifdef WIN32 -#include -#else -#include -#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/sha1.h b/sha1.h deleted file mode 100644 index cab6ff77d..000000000 --- a/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/statistics.c b/statistics.c deleted file mode 100644 index 19fd350eb..000000000 --- a/statistics.c +++ /dev/null @@ -1,379 +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 -#include - -#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; - -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; - int 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; - - *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); - - size = sizeof(stats_t) * (dive_table.nr + 1); - stats_yearly = malloc(size); - stats_monthly = malloc(size); - stats_by_trip = malloc(size); - if (!stats_yearly || !stats_monthly || !stats_by_trip) - return; - memset(stats_yearly, 0, size); - memset(stats_monthly, 0, size); - memset(stats_by_trip, 0, size); - stats_yearly[0].is_year = 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; - - 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, int 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 == 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 */ - int 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/statistics.h b/statistics.h deleted file mode 100644 index dbab25761..000000000 --- a/statistics.h +++ /dev/null @@ -1,58 +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 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, int 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/strndup.h b/strndup.h deleted file mode 100644 index 84e18b60f..000000000 --- a/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/strtod.c b/strtod.c deleted file mode 100644 index 81e5d42d1..000000000 --- a/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 -#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/CMakeLists.txt b/subsurface-core/CMakeLists.txt new file mode 100644 index 000000000..e7f531527 --- /dev/null +++ b/subsurface-core/CMakeLists.txt @@ -0,0 +1,81 @@ +set(PLATFORM_SRC unknown_platform.c) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(PLATFORM_SRC linux.c) +elseif(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 qt-ui/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 + 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 + ${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 new file mode 100644 index 000000000..3e14bec02 --- /dev/null +++ b/subsurface-core/android.cpp @@ -0,0 +1,205 @@ +/* implements Android specific functions */ +#include "dive.h" +#include "display.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 = 8.0; + +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) +{ + /* Replace this when QtCore/QStandardPaths getExternalStorageDirectory landed */ + QAndroidJniObject externalStorage = QAndroidJniObject::callStaticObjectMethod("android/os/Environment", "getExternalStorageDirectory", "()Ljava/io/File;"); + QAndroidJniObject externalStorageAbsolute = externalStorage.callObjectMethod("getAbsolutePath", "()Ljava/lang/String;"); + QString path = externalStorageAbsolute.toString(); + QAndroidJniEnvironment env; + if (env->ExceptionCheck()) { + // FIXME: Handle exception here. + env->ExceptionClear(); + path = QString("/sdcard"); + } + 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 of all USB devices attached to Android + QAndroidJniObject deviceMap = usbManager.callObjectMethod("getDeviceList", "()Ljava/util/HashMap;"); + jint num_devices = deviceMap.callMethod("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("getVendorId", "()I"); + productid = usbDevice.callMethod("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("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("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 new file mode 100644 index 000000000..be2a2fa18 --- /dev/null +++ b/subsurface-core/checkcloudconnection.cpp @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include + +#include "pref.h" +#include "helpers.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() +{ + QTimer timer; + timer.setSingleShot(true); + QEventLoop loop; + QNetworkRequest request; + request.setRawHeader("Accept", "text/plain"); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); + request.setUrl(QString(prefs.cloud_base_url) + TEAPOT); + QNetworkAccessManager *mgr = new QNetworkAccessManager(); + reply = mgr->get(request); + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + connect(reply, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); + timer.start(5000); // 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"; + return true; + } + } else { + disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + reply->abort(); + } + 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 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 new file mode 100644 index 000000000..58a412797 --- /dev/null +++ b/subsurface-core/checkcloudconnection.h @@ -0,0 +1,22 @@ +#ifndef CHECKCLOUDCONNECTION_H +#define CHECKCLOUDCONNECTION_H + +#include +#include +#include + +#include "checkcloudconnection.h" + +class CheckCloudConnection : public QObject { + Q_OBJECT +public: + CheckCloudConnection(QObject *parent = 0); + bool checkServer(); +private: + QNetworkReply *reply; +private +slots: + void sslErrors(QList errorList); +}; + +#endif // CHECKCLOUDCONNECTION_H diff --git a/subsurface-core/cochran.c b/subsurface-core/cochran.c new file mode 100644 index 000000000..267fe2b4a --- /dev/null +++ b/subsurface-core/cochran.c @@ -0,0 +1,805 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dive.h" +#include "file.h" +#include "units.h" +#include "gettext.h" +#include "cochran.h" +#include "divelist.h" + +#include + +#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 ??? + * + * 0x45415: Time stamp 17 bytes + * 0x45426: Computer configuration page 1, 512 bytes + * 0x45626: Computer configuration page 2, 512 bytes + * + */ +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, 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) +{ + 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 new file mode 100644 index 000000000..97d4361c8 --- /dev/null +++ b/subsurface-core/cochran.h @@ -0,0 +1,44 @@ +// 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.h b/subsurface-core/color.h new file mode 100644 index 000000000..7938e59a6 --- /dev/null +++ b/subsurface-core/color.h @@ -0,0 +1,67 @@ +#ifndef COLOR_H +#define COLOR_H + +/* The colors are named by picking the closest match + from http://chir.ag/projects/name-that-color */ + +#include + +// 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) + +#endif // COLOR_H diff --git a/subsurface-core/configuredivecomputer.cpp b/subsurface-core/configuredivecomputer.cpp new file mode 100644 index 000000000..2457ffe82 --- /dev/null +++ b/subsurface-core/configuredivecomputer.cpp @@ -0,0 +1,681 @@ +#include "configuredivecomputer.h" +#include "libdivecomputer/hw.h" +#include +#include +#include +#include +#include +#include +#include +#include + +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 new file mode 100644 index 000000000..f14eeeca3 --- /dev/null +++ b/subsurface-core/configuredivecomputer.h @@ -0,0 +1,68 @@ +#ifndef CONFIGUREDIVECOMPUTER_H +#define CONFIGUREDIVECOMPUTER_H + +#include +#include +#include +#include "libdivecomputer.h" +#include "configuredivecomputerthreads.h" +#include + +#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 new file mode 100644 index 000000000..78edcb37c --- /dev/null +++ b/subsurface-core/configuredivecomputerthreads.cpp @@ -0,0 +1,1760 @@ +#include "configuredivecomputerthreads.h" +#include "libdivecomputer/hw.h" +#include "libdivecomputer.h" +#include +#include + +#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_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 = 52; + 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); + + //Skip things not supported on the sport, if its a sport. + if (m_deviceDetails->model == "Sport") { + EMIT_PROGRESS(); + EMIT_PROGRESS(); + EMIT_PROGRESS(); + } else { + READ_SETTING(OSTC3_DYNAMIC_ASCEND_RATE, dynamicAscendRate); + READ_SETTING(OSTC3_GRAPHICAL_SPEED_INDICATOR, graphicalSpeedIndicator); + READ_SETTING(OSTC3_ALWAYS_SHOW_PPO2, alwaysShowppO2); + } + +#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(); + + //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 = 51; + + //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); + + //Skip things not supported on the sport, if its a sport. + if (m_deviceDetails->model == "Sport") { + EMIT_PROGRESS(); + EMIT_PROGRESS(); + EMIT_PROGRESS(); + } else { + WRITE_SETTING(OSTC3_DYNAMIC_ASCEND_RATE, dynamicAscendRate); + WRITE_SETTING(OSTC3_GRAPHICAL_SPEED_INDICATOR, graphicalSpeedIndicator); + WRITE_SETTING(OSTC3_ALWAYS_SHOW_PPO2, alwaysShowppO2); + } + +#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(); + + //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_ostc3_device_clock(device, &time); + } + 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 = {}; + dil1.oxygen = data[97]; + dil1.helium = data[98]; + // Byte100-101: + // Gasuent 2 Default (%O2,%He) + // Byte102-103: + // Gasuent 2 Current (%O2,%He) + gas dil2 = {}; + dil2.oxygen = data[101]; + dil2.helium = data[102]; + // Byte104-105: + // Gasuent 3 Default (%O2,%He) + // Byte106-107: + // Gasuent 3 Current (%O2,%He) + gas dil3 = {}; + dil3.oxygen = data[105]; + dil3.helium = data[106]; + // Byte108-109: + // Gasuent 4 Default (%O2,%He) + // Byte110-111: + // Gasuent 4 Current (%O2,%He) + gas dil4 = {}; + dil4.oxygen = data[109]; + dil4.helium = data[110]; + // Byte112-113: + // Gasuent 5 Default (%O2,%He) + // Byte114-115: + // Gasuent 5 Current (%O2,%He) + gas dil5 = {}; + dil5.oxygen = data[113]; + dil5.helium = 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); +#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); +#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) +{ + const dc_event_progress_t *progress = (dc_event_progress_t *) data; + DeviceThread *dt = static_cast(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 new file mode 100644 index 000000000..1d7a36f9b --- /dev/null +++ b/subsurface-core/configuredivecomputerthreads.h @@ -0,0 +1,62 @@ +#ifndef CONFIGUREDIVECOMPUTERTHREADS_H +#define CONFIGUREDIVECOMPUTERTHREADS_H + +#include +#include +#include +#include "libdivecomputer.h" +#include +#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 new file mode 100644 index 000000000..03fa71a50 --- /dev/null +++ b/subsurface-core/datatrak.c @@ -0,0 +1,695 @@ +#include +#include +#include +#include + +#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 substracts + * (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 necesarily 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 new file mode 100644 index 000000000..3a37e0465 --- /dev/null +++ b/subsurface-core/datatrak.h @@ -0,0 +1,41 @@ +#ifndef DATATRAK_HEADER_H +#define DATATRAK_HEADER_H + +#include + +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 new file mode 100644 index 000000000..86acc0351 --- /dev/null +++ b/subsurface-core/deco.c @@ -0,0 +1,600 @@ +/* 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 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 +#include +#include "dive.h" +#include +#include + +#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). + unsigned 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 || !in_planner()) { + 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) +{ + 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 && in_planner()) + 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)); +} + +unsigned int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth) +{ + unsigned 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 new file mode 100644 index 000000000..08ff93422 --- /dev/null +++ b/subsurface-core/deco.h @@ -0,0 +1,19 @@ +#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; + + +#ifdef __cplusplus +} +#endif + +#endif // DECO_H diff --git a/subsurface-core/device.c b/subsurface-core/device.c new file mode 100644 index 000000000..c952c84be --- /dev/null +++ b/subsurface-core/device.c @@ -0,0 +1,180 @@ +#include +#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 recrangular 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) +{ + static struct sample fake[6]; + static struct divecomputer fakedc; + + fakedc = (*dc); + 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)); + 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 absense 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 new file mode 100644 index 000000000..9ff2ce23a --- /dev/null +++ b/subsurface-core/device.h @@ -0,0 +1,18 @@ +#ifndef DEVICE_H +#define DEVICE_H + +#ifdef __cplusplus +#include "dive.h" +extern "C" { +#endif + +extern struct divecomputer *fake_dc(struct divecomputer *dc); +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 new file mode 100644 index 000000000..1ac56375d --- /dev/null +++ b/subsurface-core/devicedetails.cpp @@ -0,0 +1,78 @@ +#include "devicedetails.h" + +// This can probably be done better by someone with better c++-FU +const struct gas zero_gas = {0}; +const struct setpoint zero_setpoint = {0}; + +DeviceDetails::DeviceDetails(QObject *parent) : + QObject(parent), + data(0), + serialNo(""), + firmwareVersion(""), + customText(""), + model(""), + syncTime(false), + gas1(zero_gas), + gas2(zero_gas), + gas3(zero_gas), + gas4(zero_gas), + gas5(zero_gas), + dil1(zero_gas), + dil2(zero_gas), + dil3(zero_gas), + dil4(zero_gas), + dil5(zero_gas), + sp1(zero_setpoint), + sp2(zero_setpoint), + sp3(zero_setpoint), + sp4(zero_setpoint), + sp5(zero_setpoint), + 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) +{ +} diff --git a/subsurface-core/devicedetails.h b/subsurface-core/devicedetails.h new file mode 100644 index 000000000..1ed9914ef --- /dev/null +++ b/subsurface-core/devicedetails.h @@ -0,0 +1,97 @@ +#ifndef DEVICEDETAILS_H +#define DEVICEDETAILS_H + +#include +#include +#include "libdivecomputer.h" + +struct gas { + unsigned char oxygen; + unsigned char helium; + unsigned char type; + unsigned char depth; +}; + +struct setpoint { + unsigned char sp; + unsigned char depth; +}; + +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; +}; + + +#endif // DEVICEDETAILS_H diff --git a/subsurface-core/display.h b/subsurface-core/display.h new file mode 100644 index 000000000..9e3e1d159 --- /dev/null +++ b/subsurface-core/display.h @@ -0,0 +1,63 @@ +#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 new file mode 100644 index 000000000..2ae84ca1e --- /dev/null +++ b/subsurface-core/dive.c @@ -0,0 +1,3465 @@ +/* dive.c */ +/* maintains the internal dive list structure */ +#include +#include +#include +#include +#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, 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); +} + +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; + 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); + STRUCTURED_LIST_COPY(struct divecomputer, s->dc.next, d->dc.next, copy_dc); + /* this only copied dive computers 2 and up. The first dive computer is part + * of the struct dive, so let's make copies of its samples and events */ + copy_samples(&s->dc, &d->dc); + copy_events(&s->dc, &d->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 + +/* 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) { + int size = sizeof(*ev) + strlen(ev->name) + 1; + struct event *new_ev = malloc(size); + memcpy(new_ev, ev, size); + *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, }; + int lasttime = 0, 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); + for (i = 0; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + int 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 fixup_pressure(struct dive *dive, struct sample *sample, enum cylinderuse cyl_use) +{ + int pressure, index; + cylinder_t *cyl; + + if (cyl_use != OXYGEN) { + pressure = sample->cylinderpressure.mbar; + index = sample->sensor; + } else { // for the CCR oxygen cylinder: + pressure = sample->o2cylinderpressure.mbar; + index = get_cylinder_idx_by_use(dive, OXYGEN); + } + if (index < 0) + return; + if (!pressure) + return; + + /* + * 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). + */ + if (sample->depth.mm < SURFACE_THRESHOLD) + return; + + /* FIXME! sensor -> cylinder mapping? */ + if (index >= MAX_CYLINDERS) + return; + cyl = dive->cylinder + index; + if (!cyl->sample_start.mbar) + cyl->sample_start.mbar = pressure; + cyl->sample_end.mbar = pressure; +} + +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; + } +} + +/* + * At high pressures air becomes less compressible, and + * does not follow the ideal gas law any more. + * + * This tries to correct for that, becoming the same + * as to_ATM() at lower pressures. + * + * THIS IS A ROUGH APPROXIMATION! The real numbers will + * depend on the exact gas mix and temperature. + */ +double surface_volume_multiplier(pressure_t pressure) +{ + double bar = pressure.mbar / 1000.0; + + if (bar > 200) + bar = 0.00038 * bar * bar + 0.51629 * bar + 81.542; + return bar_to_atm(bar); +} + +int gas_volume(cylinder_t *cyl, pressure_t p) +{ + return cyl->type.size.mliter * surface_volume_multiplier(p); +} + +int wet_volume(double cuft, pressure_t p) +{ + return cuft_to_l(cuft) * 1000 / surface_volume_multiplier(p); +} + +/* + * 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". + */ +static void match_standard_cylinder(cylinder_type_t *type) +{ + double cuft; + int psi, len; + const char *fmt; + char buffer[40], *p; + + /* Do we already have a cylinder description? */ + if (type->description) + return; + + cuft = ml_to_cuft(type->size.mliter); + cuft *= surface_volume_multiplier(type->workingpressure); + 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) { + /* confusing - we don't really start from ml but millicuft !*/ + volume_of_air = cuft_to_l(type->size.mliter); + /* milliliters at 1 atm: "true size" */ + volume = volume_of_air / surface_volume_multiplier(type->workingpressure); + 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) { + 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_dive_dc(struct dive *dive, struct divecomputer *dc) +{ + int i, j; + double depthtime = 0; + int lasttime = 0; + int lastindex = -1; + int maxdepth = dc->maxdepth.mm; + int mintemp = 0; + int lastdepth = 0; + int lasttemp = 0; + int lastpressure = 0, lasto2pressure = 0; + int pressure_delta[MAX_CYLINDERS] = { INT_MAX, }; + int first_cylinder; + + /* 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); + update_min_max_temperatures(dive, dc->watertemp); + + /* make sure we know for which tank the pressure values are intended */ + first_cylinder = explicit_first_cylinder(dive, dc); + for (i = 0; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + int time = sample->time.seconds; + int depth = sample->depth.mm; + int temp = sample->temperature.mkelvin; + int pressure = sample->cylinderpressure.mbar; + int o2_pressure = sample->o2cylinderpressure.mbar; + int index; + + if (depth < 0) { + depth = interpolate_depth(dc, i, lastdepth, lasttime, time); + sample->depth.mm = depth; + } + + /* if we have an explicit first cylinder */ + if (sample->sensor == 0 && first_cylinder != 0) + sample->sensor = first_cylinder; + + 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 (depth > SURFACE_THRESHOLD) { + if (depth > maxdepth) + maxdepth = depth; + } + + fixup_pressure(dive, sample, OC_GAS); + if (dive->dc.divemode == CCR) + fixup_pressure(dive, sample, OXYGEN); + + 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); + + depthtime += (time - lasttime) * (lastdepth + depth) / 2; + lastdepth = depth; + lasttime = time; + if (sample->cns > dive->maxcns) + dive->maxcns = sample->cns; + } + + /* 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; + } + } + + update_temperature(&dc->watertemp, mintemp); + update_depth(&dc->maxdepth, maxdepth); + if (maxdepth > dive->maxdepth.mm) + dive->maxdepth.mm = maxdepth; + 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 gass 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) { + 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 syncronizes 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) +{ + int i = 0, 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() +{ + int 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 teh 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 migth 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 migth 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_maxdepth = dc->maxdepth.mm; + 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, t, nr; + 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; + } + + // Goind 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; + } +} + +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); +} + +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); + } + } +} + +static void picture_free(struct picture *picture) +{ + if (!picture) + return; + free(picture->filename); + free(picture->hash); + free(picture); +} + +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; + } +} + +/* 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); +} + +/* always acts on the current dive */ +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--; +} + +/* 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, 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 new file mode 100644 index 000000000..cef1106fd --- /dev/null +++ b/subsurface-core/dive.h @@ -0,0 +1,894 @@ +#ifndef DIVE_H +#define DIVE_H + +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include + +#include "sha1.h" +#include "units.h" + +#ifdef __cplusplus +extern "C" { +#else +#include +#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 int wet_volume(double cuft, pressure_t p); + + +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 +}; + +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 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 int explicit_first_cylinder(struct dive *dive, struct divecomputer *dc); +extern int get_depth_at_time(struct divecomputer *dc, 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; + 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; + +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 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 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 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 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 void renumber_dives(int start_nr, bool selected_only); +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, 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); + +#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 unsigned int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth); +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; + unsigned 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 new file mode 100644 index 000000000..e4081e1cd --- /dev/null +++ b/subsurface-core/divecomputer.cpp @@ -0,0 +1,228 @@ +#include "divecomputer.h" +#include "dive.h" + +#include + +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::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::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 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 new file mode 100644 index 000000000..98d12ab8b --- /dev/null +++ b/subsurface-core/divecomputer.h @@ -0,0 +1,38 @@ +#ifndef DIVECOMPUTER_H +#define DIVECOMPUTER_H + +#include +#include +#include + +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 dcMap; + QMultiMap dcWorkingMap; +}; + +extern DiveComputerList dcList; + +#endif diff --git a/subsurface-core/divelist.c b/subsurface-core/divelist.c new file mode 100644 index 000000000..a14fabf88 --- /dev/null +++ b/subsurface-core/divelist.c @@ -0,0 +1,1141 @@ +/* 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 +#include +#include +#include +#include +#include +#include "gettext.h" +#include +#include +#include + +#include "dive.h" +#include "divelist.h" +#include "display.h" +#include "planner.h" +#include "qthelperfromc.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, j, divenr; + 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++; + 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; +} + +struct dive *merge_two_dives(struct dive *a, struct dive *b) +{ + struct dive *res; + int i, j; + 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; + + 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; + 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(); +} diff --git a/subsurface-core/divelist.h b/subsurface-core/divelist.h new file mode 100644 index 000000000..5bae09cff --- /dev/null +++ b/subsurface-core/divelist.h @@ -0,0 +1,62 @@ +#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 new file mode 100644 index 000000000..af5157f4a --- /dev/null +++ b/subsurface-core/divelogexportlogic.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include +#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 new file mode 100644 index 000000000..84f09c362 --- /dev/null +++ b/subsurface-core/divelogexportlogic.h @@ -0,0 +1,20 @@ +#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 new file mode 100644 index 000000000..e9eed2a07 --- /dev/null +++ b/subsurface-core/divesite.c @@ -0,0 +1,337 @@ +/* divesite.c */ +#include "divesite.h" +#include "dive.h" +#include "divelist.h" + +#include + +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 new file mode 100644 index 000000000..ae102a14b --- /dev/null +++ b/subsurface-core/divesite.cpp @@ -0,0 +1,31 @@ +#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 = "(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 += ")"; + return locationTag; +} diff --git a/subsurface-core/divesite.h b/subsurface-core/divesite.h new file mode 100644 index 000000000..f18b2e8e8 --- /dev/null +++ b/subsurface-core/divesite.h @@ -0,0 +1,80 @@ +#ifndef DIVESITE_H +#define DIVESITE_H + +#include "units.h" +#include "taxonomy.h" +#include + +#ifdef __cplusplus +#include +extern "C" { +#else +#include +#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 new file mode 100644 index 000000000..3542f96fa --- /dev/null +++ b/subsurface-core/divesitehelpers.cpp @@ -0,0 +1,208 @@ +// +// infrastructure to deal with dive sites +// + +#include "divesitehelpers.h" + +#include "divesite.h" +#include "helpers.h" +#include "membuffer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct GeoLookupInfo { + degrees_t lat; + degrees_t lon; + uint32_t uuid; +}; + +QVector 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 new file mode 100644 index 000000000..a08069bc0 --- /dev/null +++ b/subsurface-core/divesitehelpers.h @@ -0,0 +1,18 @@ +#ifndef DIVESITEHELPERS_H +#define DIVESITEHELPERS_H + +#include "units.h" +#include + +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 new file mode 100644 index 000000000..47c439735 --- /dev/null +++ b/subsurface-core/equipment.c @@ -0,0 +1,235 @@ +/* equipment.c */ +#include +#include +#include +#include +#include +#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 new file mode 100644 index 000000000..0b1cda2bc --- /dev/null +++ b/subsurface-core/exif.cpp @@ -0,0 +1,587 @@ +#include +/************************************************************************** + 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 +#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 new file mode 100644 index 000000000..0fb3a7d4a --- /dev/null +++ b/subsurface-core/exif.h @@ -0,0 +1,147 @@ +/************************************************************************** + 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 + +// +// 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 new file mode 100644 index 000000000..c4032c1f2 --- /dev/null +++ b/subsurface-core/file.c @@ -0,0 +1,1066 @@ +#include +#include +#include +#include +#include +#include +#include "gettext.h" +#include +#include + +#include "dive.h" +#include "file.h" +#include "git-access.h" +#include "qthelperfromc.h" + +/* For SAMPLE_* */ +#include + +/* to check XSLT version number */ +#include + +/* 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 == mem->size) + 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, struct memblock *mem) +{ + 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); + } + 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", 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) +{ + 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(const char *filename, 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, mem); + + /* 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(filename, mem, CSV_DEPTH); + if (!strcasecmp(fmt, "LVD")) + return try_to_open_liquivision(filename, mem); + if (!strcasecmp(fmt, "TMP")) + return try_to_open_csv(filename, mem, CSV_TEMP); + if (!strcasecmp(fmt, "HP1")) + return try_to_open_csv(filename, 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 parse_file(const char *filename) +{ + struct git_repository *git; + const char *branch; + 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 */ + return 0; + + if (git && !git_load_dives(git, branch)) + return 0; + + 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. + */ + + if (verbose >= 2) { + fprintf(stderr, "(echo ''; cat %s;echo '') | 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); + } + + 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 new file mode 100644 index 000000000..855109960 --- /dev/null +++ b/subsurface-core/file.h @@ -0,0 +1,24 @@ +#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, struct memblock *mem); +#ifdef __cplusplus +} +#endif + +#endif // FILE_H diff --git a/subsurface-core/gaspressures.c b/subsurface-core/gaspressures.c new file mode 100644 index 000000000..5f46d6080 --- /dev/null +++ b/subsurface-core/gaspressures.c @@ -0,0 +1,435 @@ +/* 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, int pressure) +{ // 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; + if (entry->sec >= segment->t_end) { + interpolate.pressure_time += entry->pressure_time; + break; + } + if (entry->sec == segment->t_start) { + interpolate.acc_pressure_time = 0; + interpolate.pressure_time = 0; + if (pressure) + interpolate.start = pressure; + continue; + } + if (i < cur) { + if (pressure) { + interpolate.start = pressure; + interpolate.acc_pressure_time = 0; + interpolate.pressure_time = 0; + } else { + interpolate.acc_pressure_time += entry->pressure_time; + interpolate.pressure_time += entry->pressure_time; + } + continue; + } + if (i == cur) { + interpolate.acc_pressure_time += entry->pressure_time; + interpolate.pressure_time += entry->pressure_time; + continue; + } + interpolate.pressure_time += entry->pressure_time; + if (pressure) { + interpolate.end = pressure; + break; + } + } + 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; + 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; + pr_interpolate_t interpolate; + 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, + 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 .. + interpolate = get_pr_interpolate_data(segment, pi, i, pressure); // Set up an interpolation structure + 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 divecomputer *dc, 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) +{ + int i, cylinderid, cylinderindex = -1; + pr_track_t *track_pr[MAX_CYLINDERS] = { NULL, }; + pr_track_t *current = NULL; + bool missing_pr = false; + + 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, dc, 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 new file mode 100644 index 000000000..420c117a2 --- /dev/null +++ b/subsurface-core/gaspressures.h @@ -0,0 +1,35 @@ +#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 new file mode 100644 index 000000000..43ff023c7 --- /dev/null +++ b/subsurface-core/gettext.h @@ -0,0 +1,10 @@ +#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 new file mode 100644 index 000000000..c579e3c3c --- /dev/null +++ b/subsurface-core/gettextfromc.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +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 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 new file mode 100644 index 000000000..53df18365 --- /dev/null +++ b/subsurface-core/gettextfromc.h @@ -0,0 +1,18 @@ +#ifndef GETTEXTFROMC_H +#define GETTEXTFROMC_H + +#include +#include + +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 translationCache; +}; + +#endif // GETTEXTFROMC_H diff --git a/subsurface-core/git-access.c b/subsurface-core/git-access.c new file mode 100644 index 000000000..607789f98 --- /dev/null +++ b/subsurface-core/git-access.c @@ -0,0 +1,891 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dive.h" +#include "membuffer.h" +#include "strndup.h" +#include "qthelperfromc.h" +#include "git-access.h" +#include "gettext.h" + +/* + * The libgit2 people are incompetent at making libraries. They randomly change + * the interfaces, often just renaming things without any sane way to know which + * version you should check for etc etc. It's a disgrace. + */ +#if !LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR < 22 + #define git_remote_lookup(res, repo, name) git_remote_load(res, repo, name) + #if LIBGIT2_VER_MINOR <= 20 + #define git_remote_fetch(remote, refspecs, signature, reflog) git_remote_fetch(remote) + #else + #define git_remote_fetch(remote, refspecs, signature, reflog) git_remote_fetch(remote, signature, reflog) + #endif +#endif + +#if !USE_LIBGIT23_API && !LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR == 22 + #define git_remote_push(remote,refspecs,opts) git_remote_push(remote,refspecs,opts,NULL,NULL) + #define git_reference_set_target(out,ref,id,log_message) git_reference_set_target(out,ref,id,NULL,log_message) + #define git_reset(repo,target,reset_type,checkout_opts) git_reset(repo,target,reset_type,checkout_opts,NULL,NULL) +#endif + +/* + * api break introduced in libgit2 master after 0.22 - let's guess this is the v0.23 API + */ +#if USE_LIBGIT23_API || (!LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR >= 23) + #define git_branch_create(out, repo, branch_name, target, force, signature, log_message) \ + git_branch_create(out, repo, branch_name, target, force) +#endif + +bool is_subsurface_cloud = false; + +int (*update_progress_cb)(int) = NULL; + +void set_git_update_cb(int(*cb)(int)) +{ + update_progress_cb = cb; +} + +static int update_progress(int percent) +{ + static int last_percent = -10; + int ret = 0; + if (update_progress_cb) + ret = (*update_progress_cb)(percent); + if (verbose && percent - 10 >= last_percent) { + last_percent = percent; + fprintf(stderr, "git progress %d%%\n", percent); + } + return ret; +} + +// the checkout_progress_cb doesn't allow canceling of the operation +static void progress_cb(const char *path, size_t completed_steps, size_t total_steps, void *payload) +{ + int percent = 0; + if (total_steps) + percent = 100 * completed_steps / total_steps; + (void)update_progress(percent); +} + +// this randomly assumes that 80% of the time is spent on the objects and 20% on the deltas +// if the user cancels the dialog this is passed back to libgit2 +static int transfer_progress_cb(const git_transfer_progress *stats, void *payload) +{ + int percent = 0; + if (stats->total_objects) + percent = 80 * stats->received_objects / stats->total_objects; + if (stats->total_deltas) + percent += 20 * stats->indexed_deltas / stats->total_deltas; + return update_progress(percent); +} + +static int push_transfer_progress_cb(unsigned int current, unsigned int total, size_t bytes, void *payload) +{ + int percent = 0; + if (total != 0) + percent = 100 * current / total; + return update_progress(percent); +} + +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) +{ + 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; +} + +#if USE_LIBGIT23_API +int credential_ssh_cb(git_cred **out, + const char *url, + const char *username_from_url, + unsigned int 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) +{ + 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) +{ + 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; +} + +#endif + +static int update_remote(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, enum remote_transport rt) +{ + 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; + +#if USE_LIBGIT23_API + 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; +#endif + 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, git_reference *remote, git_oid *base, const git_oid *local_id, const git_oid *remote_id) +{ + 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); +#ifdef USE_LIBGIT23_API + merge_options.tree_flags = GIT_MERGE_TREE_FIND_RENAMES; +#else + merge_options.flags = GIT_MERGE_TREE_FIND_RENAMES; +#endif + 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) && !git_repository_is_bare(repo)) { + git_object *parent; + git_reference_peel(&parent, local, GIT_OBJ_COMMIT); + if (update_git_checkout(repo, parent, merged_tree)) { + goto write_error; + } + } + if (git_reference_set_target(&local, local, &commit_oid, "Subsurface merge event")) + goto write_error; + set_git_id(&commit_oid); + git_signature_free(author); + + 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; + + if (verbose) + fprintf(stderr, "git storage: try to update\n"); + + 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)) + 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 */ + return try_to_git_merge(repo, local, remote, &base, local_id, remote_id); + +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"); + + 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); +#if USE_LIBGIT23_API + 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; + error = git_remote_push(origin, &refspec, &opts); +#else + error = git_remote_push(origin, &refspec, NULL); +#endif + } 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 (verbose) + fprintf(stderr, "sync with remote %s[%s]\n", remote, branch); + + 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"); + return 0; + } + if (verbose) + fprintf(stderr, "git storage: fetch remote\n"); +#if USE_LIBGIT23_API + 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; + error = git_remote_fetch(origin, NULL, &opts, NULL); +#else + error = git_remote_fetch(origin, NULL, NULL, NULL); +#endif + // 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); + 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) +{ + 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"); + +#if USE_LIBGIT23_API + 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; +#endif + 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) + } 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 storage: accessing %s\n", remote); + } + /* 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); + } + return create_local_repo(localdir, remote, branch, rt); +} + +/* + * 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 new file mode 100644 index 000000000..a2a9ba3ae --- /dev/null +++ b/subsurface-core/git-access.h @@ -0,0 +1,31 @@ +#ifndef GITACCESS_H +#define GITACCESS_H + +#include "git2.h" + +#ifdef __cplusplus +extern "C" { +#else +#include +#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 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(*cb)(int)); +char *get_local_dir(const char *remote, const char *branch); +#ifdef __cplusplus +} +#endif +#endif // GITACCESS_H + diff --git a/subsurface-core/helpers.h b/subsurface-core/helpers.h new file mode 100644 index 000000000..5f2f2f2c5 --- /dev/null +++ b/subsurface-core/helpers.h @@ -0,0 +1,52 @@ +/* + * helpers.h + * + * header file for random helper functions of Subsurface + * + */ +#ifndef HELPERS_H +#define HELPERS_H + +#include +#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, int mbar = 0); +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 parseTemperatureToMkelvin(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(); +QString getDateFormat(); +void selectedDivesGasUsed(QVector > &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/libdivecomputer.c b/subsurface-core/libdivecomputer.c new file mode 100644 index 000000000..ca8378379 --- /dev/null +++ b/subsurface-core/libdivecomputer.c @@ -0,0 +1,1083 @@ +#include +#include +#include +#include +#include "gettext.h" +#include "dive.h" +#include "device.h" +#include "divelist.h" +#include "display.h" + +#include "libdivecomputer.h" +#include +#include + + +/* 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, int ngases) +{ + static bool shown_warning = false; + int i, rc; + +#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) + int ntanks = 0; + rc = dc_parser_get_field(parser, DC_FIELD_TANK_COUNT, 0, &ntanks); + if (rc == DC_STATUS_SUCCESS) { + if (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(" ", 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("\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, ...) +{ + 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) +{ + // 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; +} + +static inline int year(int year) +{ + if (year < 70) + return year + 2000; + if (year < 100) + return year + 1900; + return year; +} + +/* + * 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 (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) +{ + 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) +{ + 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) +{ + 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) { +#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) { + 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 new file mode 100644 index 000000000..79817e509 --- /dev/null +++ b/subsurface-core/libdivecomputer.h @@ -0,0 +1,66 @@ +#ifndef LIBDIVECOMPUTER_H +#define LIBDIVECOMPUTER_H + + +/* libdivecomputer */ +#include +#include +#include + +#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 new file mode 100644 index 000000000..d4131c7ea --- /dev/null +++ b/subsurface-core/linux.c @@ -0,0 +1,225 @@ +/* linux.c */ +/* implements Linux specific functions */ +#include "dive.h" +#include "display.h" +#include "membuffer.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// 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 + 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 = { 0 }; + 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 >= 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) +{ + /* NOP */ +} + +void subsurface_console_exit(void) +{ + /* NOP */ +} diff --git a/subsurface-core/liquivision.c b/subsurface-core/liquivision.c new file mode 100644 index 000000000..295287c15 --- /dev/null +++ b/subsurface-core/liquivision.c @@ -0,0 +1,414 @@ +#include + +#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) +{ + // 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 = "Xen"; + break; + case 1: + case 2: + dc->model = "Xeo"; + break; + case 4: + dc->model = "Lynx"; + break; + default: + dc->model = "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) +{ + 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 new file mode 100644 index 000000000..39dab4367 --- /dev/null +++ b/subsurface-core/load-git.c @@ -0,0 +1,1638 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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) +{ + 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) +{ + 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) +{ struct dive *dive = _dive; dive->divemaster = get_utf8(str); } + +static void parse_dive_buddy(char *line, struct membuffer *str, void *_dive) +{ struct dive *dive = _dive; dive->buddy = get_utf8(str); } + +static void parse_dive_suit(char *line, struct membuffer *str, void *_dive) +{ struct dive *dive = _dive; dive->suit = get_utf8(str); } + +static void parse_dive_notes(char *line, struct membuffer *str, void *_dive) +{ struct dive *dive = _dive; dive->notes = get_utf8(str); } + +static void parse_dive_divesiteid(char *line, struct membuffer *str, void *_dive) +{ 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) +{ + 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) +{ struct dive *dive = _dive; dive->airtemp = get_temperature(line); } + +static void parse_dive_watertemp(char *line, struct membuffer *str, void *_dive) +{ struct dive *dive = _dive; dive->watertemp = get_temperature(line); } + +static void parse_dive_duration(char *line, struct membuffer *str, void *_dive) +{ struct dive *dive = _dive; dive->duration = get_duration(line); } + +static void parse_dive_rating(char *line, struct membuffer *str, void *_dive) +{ struct dive *dive = _dive; dive->rating = get_index(line); } + +static void parse_dive_visibility(char *line, struct membuffer *str, void *_dive) +{ struct dive *dive = _dive; dive->visibility = get_index(line); } + +static void parse_dive_notrip(char *line, struct membuffer *str, void *_dive) +{ struct dive *dive = _dive; dive->tripflag = NO_TRIP; } + +static void parse_site_description(char *line, struct membuffer *str, void *_ds) +{ struct dive_site *ds = _ds; ds->description = strdup(mb_cstring(str)); } + +static void parse_site_name(char *line, struct membuffer *str, void *_ds) +{ struct dive_site *ds = _ds; ds->name = strdup(mb_cstring(str)); } + +static void parse_site_notes(char *line, struct membuffer *str, void *_ds) +{ 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) +{ + 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) +{ struct divecomputer *dc = _dc; dc->airtemp = get_temperature(line); } + +static void parse_dc_date(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; update_date(&dc->when, line); } + +static void parse_dc_deviceid(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->deviceid = get_hex(line); } + +static void parse_dc_diveid(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->diveid = get_hex(line); } + +static void parse_dc_duration(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->duration = get_duration(line); } + +static void parse_dc_dctype(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->divemode = get_dctype(line); } + +static void parse_dc_maxdepth(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->maxdepth = get_depth(line); } + +static void parse_dc_meandepth(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->meandepth = get_depth(line); } + +static void parse_dc_model(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->model = get_utf8(str); } + +static void parse_dc_numberofoxygensensors(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->no_o2sensors = get_index(line); } + +static void parse_dc_surfacepressure(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->surface_pressure = get_pressure(line); } + +static void parse_dc_salinity(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->salinity = get_salinity(line); } + +static void parse_dc_surfacetime(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; dc->surfacetime = get_duration(line); } + +static void parse_dc_time(char *line, struct membuffer *str, void *_dc) +{ struct divecomputer *dc = _dc; update_time(&dc->when, line); } + +static void parse_dc_watertemp(char *line, struct membuffer *str, void *_dc) +{ 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); + 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) +{ dive_trip_t *trip = _trip; update_date(&trip->when, line); } + +static void parse_trip_time(char *line, struct membuffer *str, void *_trip) +{ dive_trip_t *trip = _trip; update_time(&trip->when, line); } + +static void parse_trip_location(char *line, struct membuffer *str, void *_trip) +{ dive_trip_t *trip = _trip; trip->location = get_utf8(str); } + +static void parse_trip_notes(char *line, struct membuffer *str, void *_trip) +{ dive_trip_t *trip = _trip; trip->notes = get_utf8(str); } + +static void parse_settings_autogroup(char *line, struct membuffer *str, void *_unused) +{ set_autogroup(1); } + +static void parse_settings_units(char *line, struct membuffer *str, void *unused) +{ + if (line) + set_informational_units(line); +} + +static void parse_settings_userid(char *line, struct membuffer *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) +{ + 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) +{ } + +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) +{ + 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) +{ + struct picture *pic = _pic; + pic->filename = get_utf8(str); +} + +static void parse_picture_gps(char *line, struct membuffer *str, void *_pic) +{ + 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) +{ + 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) +{ + 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 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)); + return GIT_WALK_OK; +} + +static int picture_directory(const char *root, const char *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, 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) +{ + 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; +} + +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- */ + 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 do_git_load(git_repository *repo, const char *branch) +{ + int ret; + git_object *object; + git_commit *commit; + git_tree *tree; + + if (git_revparse_single(&object, repo, branch)) + return report_error("Unable to look up revision '%s'", branch); + if (git_object_peel((git_object **)&commit, object, GIT_OBJ_COMMIT)) + return report_error("Revision '%s' is not a valid commit", branch); + 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; +} + +/* + * 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 new file mode 100644 index 000000000..aa2be4b3b --- /dev/null +++ b/subsurface-core/macos.c @@ -0,0 +1,206 @@ +/* macos.c */ +/* implements Mac OS X specific functions */ +#include +#include +#include +#include "dive.h" +#include "display.h" +#include +#include +#include +#include +#include +#include +#include + +void subsurface_user_info(struct user_info *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) +{ + // 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 >= 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 >= 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) +{ + /* NOP */ +} + +void subsurface_console_exit(void) +{ + /* NOP */ +} diff --git a/subsurface-core/membuffer.c b/subsurface-core/membuffer.c new file mode 100644 index 000000000..2889a0cdc --- /dev/null +++ b/subsurface-core/membuffer.c @@ -0,0 +1,285 @@ +#include +#include +#include +#include + +#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 = "
"; + } + 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 new file mode 100644 index 000000000..434b34c71 --- /dev/null +++ b/subsurface-core/membuffer.h @@ -0,0 +1,74 @@ +#ifndef MEMBUFFER_H +#define MEMBUFFER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +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/ostctools.c b/subsurface-core/ostctools.c new file mode 100644 index 000000000..4b4cff241 --- /dev/null +++ b/subsurface-core/ostctools.c @@ -0,0 +1,193 @@ +#include +#include +#include + +#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 necesary 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 new file mode 100644 index 000000000..3d86222b9 --- /dev/null +++ b/subsurface-core/parse-xml.c @@ -0,0 +1,3641 @@ +#include +#include +#include +#include +#include +#include +#include +#define __USE_XOPEN +#include +#include +#include +#include +#include +#include + +#include "gettext.h" + +#include "dive.h" +#include "divelist.h" +#include "device.h" +#include "membuffer.h" + +int verbose, quit; +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) +{ + /* 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 + * + * 32.0 + * + * 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) +{ + 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 (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("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 (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) +{ + 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); + 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, ""); + + if (ret) { + xmlParserCtxtPtr ctx; + char buf[] = ""; + int 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) +{ + 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) +{ + dive_start(); + divedate(starttime, &cur_dive->when); + dive_end(); +} + +extern int dm4_events(void *handle, int columns, char **data, char **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) +{ + 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) +{ + 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) +{ + 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) +{ + int i, 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; inumber = 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + return 0; +} + +extern int cobalt_location(void *handle, int columns, char **data, char **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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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 new file mode 100644 index 000000000..22d37165a --- /dev/null +++ b/subsurface-core/planner.c @@ -0,0 +1,1455 @@ +/* planner.c + * + * code that allows us to plan future dives + * + * (c) Dirk Hohndel 2013 + */ +#include +#include +#include +#include +#include "dive.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) +{ + int 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 unsigned int *sort_stops(int *dstops, int dnr, struct gaschanges *gstops, int gnr) +{ + int i, gi, di; + int total = dnr + gnr; + unsigned 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 buf[1000], *deco; + 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, "%s %s
", + translate("gettextFromC", "Warning:"), temp); + dive->notes = strdup(buffer); + + free((void *)buffer); + free((void *)temp); + return; + } + + len = show_disclaimer ? snprintf(buffer, sz_buffer, "
%s

", 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, "
%s
%s

", + translate("gettextFromC", "Subsurface dive plan"), temp); + + if (!plan_verbatim) { + len += snprintf(buffer + len, sz_buffer - len, "
", + translate("gettextFromC", "depth")); + if (plan_display_duration) + len += snprintf(buffer + len, sz_buffer - len, "", + translate("gettextFromC", "duration")); + if (plan_display_runtime) + len += snprintf(buffer + len, sz_buffer - len, "", + translate("gettextFromC", "runtime")); + len += snprintf(buffer + len, sz_buffer - len, + "", + 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
", 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
", 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 )) { + snprintf(temp, sz_temp, translate("gettextFromC", "%3.0f%s"), depthvalue, depth_unit); + len += snprintf(buffer + len, sz_buffer - len, "", temp); + if (plan_display_duration) { + snprintf(temp, sz_temp, translate("gettextFromC", "%3dmin"), (dp->time - lasttime + 30) / 60); + len += snprintf(buffer + len, sz_buffer - len, "", temp); + } + if (plan_display_runtime) { + snprintf(temp, sz_temp, translate("gettextFromC", "%3dmin"), (dp->time + 30) / 60); + len += snprintf(buffer + len, sz_buffer - len, "", 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, "", gasname(&newgasmix), + temp); + } else { + len += snprintf(buffer + len, sz_buffer - len, "", 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, "", gasname(&gasmix), + temp); + } else { + len += snprintf(buffer + len, sz_buffer - len, "", 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, ""); + } + len += snprintf(buffer + len, sz_buffer - len, ""); + 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
", temp); + } + gaschange_after = false; + gasmix = newgasmix; + } + } + lastprintdepth = newdepth; + lastdepth = dp->depth; + lastsetpoint = dp->setpoint; + lastentered = dp->entered; + } while ((dp = nextdp) != NULL); + len += snprintf(buffer + len, sz_buffer - len, "
%s%s%s%s
%s%s%s%s %s%s%s %s%s 
"); + + 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, "

%s: %i%%", temp, dive->cns); + snprintf(temp, sz_temp, "%s", translate("gettextFromC", "OTU")); + len += snprintf(buffer + len, sz_buffer - len, "
%s: %i
", 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, "

%s
", 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), " — %s %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), " — %s %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
", 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, "%s %s
", + 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, "%s %s
", + translate("gettextFromC", "Warning:"), temp); + + } + } + dp = dp->next; + } + } + snprintf(buffer + len, sz_buffer - len, "
"); + dive->notes = strdup(buffer); + + free((void *)buffer); + free((void *)temp); +} + +int ascent_velocity(int depth, int avg_depth, int 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; + unsigned int stopidx; + int depth; + struct gaschanges *gaschanges = NULL; + int gaschangenr; + int *decostoplevels; + int decostoplevelcount; + unsigned 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 new file mode 100644 index 000000000..a675989e0 --- /dev/null +++ b/subsurface-core/planner.h @@ -0,0 +1,32 @@ +#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/pref.h b/subsurface-core/pref.h new file mode 100644 index 000000000..84975aaaa --- /dev/null +++ b/subsurface-core/pref.h @@ -0,0 +1,158 @@ +#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; + +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; + 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; +}; +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 new file mode 100644 index 000000000..fe459d3da --- /dev/null +++ b/subsurface-core/prefs-macros.h @@ -0,0 +1,68 @@ +#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 new file mode 100644 index 000000000..d39133c21 --- /dev/null +++ b/subsurface-core/profile.c @@ -0,0 +1,1463 @@ +/* profile.c */ +/* creates all the necessary data for drawing the dive profile + */ +#include "gettext.h" +#include +#include +#include + +#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); + +#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 { + /* 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)); + } +} + +/* 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++; +} + +/* 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, unsigned 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, unsigned 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, unsigned 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, ~0u); + + 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, ~0u); +} + +static void check_setpoint_events(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) +{ + int i = 0; + pressure_t setpoint; + + 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, ~0u); +} + + +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; + 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; + + 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 && 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 && ev->time.seconds == lasttime + offset) + ev = ev->next; + } + + /* Add events if they are between plot entries */ + while (ev && 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 && 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; + int duration; + + 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) +{ + int i = 0, last = 0; + struct plot_data *last_entry = NULL; + + for (i = 0; i < pi->nr; i++) + fill_sac(dive, pi, i); +} + +static void populate_secondary_sensor_data(struct divecomputer *dc, struct plot_info *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); +} + +/* 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 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; + 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; + 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 + entry->ceiling = deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), surface_pressure, dive, !prefs.calcceiling3m); + 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 (prefs.calcndltts && !print_mode) { + /* only calculate ndl/tts on every 30 seconds */ + if ((entry->sec - last_ndl_tts_calc_time) < 30) { + 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); + /* Restore "real" deco state for next real time step */ + restore_deco_state(cache_data); + free(cache_data); + } + } +#if DECO_CALC_DEBUG & 1 + dump_tissues(); +#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; + init_decompression(dive); + /* 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 */ + calculate_deco_information(dive, dc, pi, false); /* and ceiling information, using gradient factor values in Preferences) */ + 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 new file mode 100644 index 000000000..a6dbfcf5f --- /dev/null +++ b/subsurface-core/profile.h @@ -0,0 +1,109 @@ +#ifndef PROFILE_H +#define PROFILE_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 new file mode 100644 index 000000000..ca038b145 --- /dev/null +++ b/subsurface-core/qt-gui.h @@ -0,0 +1,17 @@ +#ifndef QT_GUI_H +#define QT_GUI_H + +#include + +void init_qt_late(); +void init_ui(); + +void run_ui(); +void exit_ui(); + +#if defined(SUBSURFACE_MOBILE) +#include +extern QObject *qqWindowObject; +#endif + +#endif // QT_GUI_H diff --git a/subsurface-core/qt-init.cpp b/subsurface-core/qt-init.cpp new file mode 100644 index 000000000..b52dfd970 --- /dev/null +++ b/subsurface-core/qt-init.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#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 new file mode 100644 index 000000000..a12d25333 --- /dev/null +++ b/subsurface-core/qthelper.cpp @@ -0,0 +1,1653 @@ +#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 +#include +#include "file.h" +#include "prefs-macros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +const char *existing_filename; +static QString shortDateFormat; +static QString dateFormat; +static QString timeFormat; +static QLocale loc; + +#define translate(_context, arg) trGettext(arg) +static const QString DEGREE_SIGNS("dD" UTF8_DEGREE); + +Dive::Dive() : + m_number(-1), + dive(NULL) +{ +} + +Dive::~Dive() +{ +} + +int Dive::number() const +{ + return m_number; +} + +int Dive::id() const +{ + return m_id; +} + +QString Dive::date() const +{ + return m_date; +} + +timestamp_t Dive::timestamp() const +{ + return m_timestamp; +} + +QString Dive::time() const +{ + return m_time; +} + +QString Dive::location() const +{ + return m_location; +} + +QString Dive::duration() const +{ + return m_duration; +} + +QString Dive::depth() const +{ + return m_depth; +} + +QString Dive::divemaster() const +{ + return m_divemaster; +} + +QString Dive::buddy() const +{ + return m_buddy; +} + +QString Dive::airTemp() const +{ + return m_airTemp; +} + +QString Dive::waterTemp() const +{ + return m_waterTemp; +} + +QString Dive::notes() const +{ + return m_notes; +} + +QString Dive::tags() const +{ + return m_tags; +} + +QString Dive::gas() const +{ + return m_gas; +} + +QString Dive::sac() const +{ + return m_sac; +} + +QString Dive::weight() const +{ + return m_weight; +} + +QString Dive::suit() const +{ + return m_suit; +} + +QString Dive::cylinder() const +{ + return m_cylinder; +} + +QString Dive::trip() const +{ + return m_trip; +} + +int Dive::rating() const +{ + return m_rating; +} + +void Dive::put_divemaster() +{ + if (!dive->divemaster) + m_divemaster = "--"; + else + m_divemaster = dive->divemaster; +} + +void Dive::put_date_time() +{ + QDateTime localTime = QDateTime::fromTime_t(dive->when - gettimezoneoffset(displayed_dive.when)); + localTime.setTimeSpec(Qt::UTC); + m_date = localTime.date().toString(dateFormat); + m_time = localTime.time().toString(timeFormat); +} + +void Dive::put_timestamp() +{ + m_timestamp = dive->when; +} + +void Dive::put_location() +{ + m_location = QString::fromUtf8(get_dive_location(dive)); + if (m_location.isEmpty()) { + m_location = "--"; + } +} + +void Dive::put_depth() +{ + m_depth = get_depth_string(dive->dc.maxdepth.mm, true, true); +} + +void Dive::put_duration() +{ + m_duration = get_dive_duration_string(dive->duration.seconds, QObject::tr("h:"), QObject::tr("min")); +} + +void Dive::put_buddy() +{ + if (!dive->buddy) + m_buddy = "--"; + else + m_buddy = dive->buddy; +} + +void Dive::put_temp() +{ + m_airTemp = get_temperature_string(dive->airtemp, true); + m_waterTemp = get_temperature_string(dive->watertemp, true); + if (m_airTemp.isEmpty()) { + m_airTemp = "--"; + } + if (m_waterTemp.isEmpty()) { + m_waterTemp = "--"; + } +} + +void Dive::put_notes() +{ + if (same_string(dive->dc.model, "planned dive")) { + QTextDocument notes; + notes.setHtml(QString::fromUtf8(dive->notes)); + m_notes = notes.toPlainText(); + } else { + m_notes = QString::fromUtf8(dive->notes); + } + if (m_notes.isEmpty()) { + m_notes = "--"; + } +} + +void Dive::put_tags() +{ + char buffer[256]; + taglist_get_tagstring(dive->tag_list, buffer, 256); + m_tags = QString(buffer); +} + +void Dive::put_gas() +{ + int added = 0; + QString gas, gases; + for (int i = 0; i < MAX_CYLINDERS; i++) { + if (!is_cylinder_used(dive, i)) + continue; + gas = dive->cylinder[i].type.description; + gas += QString(!gas.isEmpty() ? " " : "") + gasname(&dive->cylinder[i].gasmix); + // if has a description and if such gas is not already present + if (!gas.isEmpty() && gases.indexOf(gas) == -1) { + if (added > 0) + gases += QString(" / "); + gases += gas; + added++; + } + } + m_gas = gases; +} + +void Dive::put_sac() +{ + if (dive->sac) { + const char *unit; + int decimal; + double value = get_volume_units(dive->sac, &decimal, &unit); + m_sac = QString::number(value, 'f', decimal).append(unit); + } +} + +void Dive::put_weight() +{ + weight_t tw = { total_weight(dive) }; + m_weight = weight_string(tw.grams); +} + +void Dive::put_suit() +{ + m_suit = QString(dive->suit); +} + +void Dive::put_cylinder() +{ + m_cylinder = QString(dive->cylinder[0].type.description); +} + +void Dive::put_trip() +{ + dive_trip *trip = dive->divetrip; + if (trip) { + m_trip = QString(trip->location); + } +} + +QString weight_string(int weight_in_grams) +{ + QString str; + if (get_units()->weight == units::KG) { + int gr = weight_in_grams % 1000; + int kg = weight_in_grams / 1000; + if (kg >= 20.0) { + str = QString("0"); + } else { + str = QString("%1.%2").arg(kg).arg((unsigned)(gr) / 100); + } + } else { + double lbs = grams_to_lbs(weight_in_grams); + str = QString("%1").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1); + } + return (str); +} + +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 getDivesInTrip(dive_trip_t *trip) +{ + QList 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 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 &a, const QPair &b) +{ + return a.second < b.second; +} + +void selectedDivesGasUsed(QVector > &gasUsedOrdered) +{ + int i, j; + struct dive *d; + QMap gasUsed; + for_each_dive (i, d) { + if (!d->selected) + continue; + volume_t diveGases[MAX_CYLINDERS] = {}; + get_gas_used(d, diveGases); + for (j = 0; j < MAX_CYLINDERS; j++) + if (diveGases[j].mliter) { + QString gasName = gasname(&d->cylinder[j].gasmix); + gasUsed[gasName] += diveGases[j].mliter; + } + } + Q_FOREACH(const QString& gas, gasUsed.keys()) { + gasUsedOrdered.append(qMakePair(gas, gasUsed[gas])); + } + qSort(gasUsedOrdered.begin(), gasUsedOrdered.end(), lessThan); +} + +QString getUserAgent() +{ + QString arch; + // fill in the system data - use ':' as separator + // replace all other ':' with ' ' so that this is easy to parse + QString userAgent = QString("Subsurface:%1:").arg(subsurface_version()); + userAgent.append(SubsurfaceSysInfo::prettyOsName().replace(':', ' ') + ":"); + arch = SubsurfaceSysInfo::buildCpuArchitecture().replace(':', ' '); + userAgent.append(arch); + if (arch == "i386") + userAgent.append("/" + SubsurfaceSysInfo::currentCpuArchitecture()); + userAgent.append(":" + uiLanguage(NULL)); + return userAgent; + +} + +QString uiLanguage(QLocale *callerLoc) +{ + QSettings s; + s.beginGroup("Language"); + + if (!s.value("UseSystemLanguage", true).toBool()) { + loc = QLocale(s.value("UiLanguage", QLocale().uiLanguages().first()).toString()); + } else { + loc = QLocale(QLocale().uiLanguages().first()); + } + + QString uiLang = loc.uiLanguages().first(); + s.endGroup(); + + // there's a stupid Qt bug on MacOS where uiLanguages doesn't give us the country info + if (!uiLang.contains('-') && uiLang != loc.bcp47Name()) { + QLocale loc2(loc.bcp47Name()); + loc = loc2; + uiLang = loc2.uiLanguages().first(); + } + if (callerLoc) + *callerLoc = loc; + + // the short format is fine + // the long format uses long weekday and month names, so replace those with the short ones + // for time we don't want the time zone designator and don't want leading zeroes on the hours + shortDateFormat = loc.dateFormat(QLocale::ShortFormat); + dateFormat = loc.dateFormat(QLocale::LongFormat); + dateFormat.replace("dddd,", "ddd").replace("dddd", "ddd").replace("MMMM", "MMM"); + // special hack for Swedish as our switching from long weekday names to short weekday names + // messes things up there + dateFormat.replace("'en' 'den' d:'e'", " d"); + timeFormat = loc.timeFormat(); + timeFormat.replace("(t)", "").replace(" t", "").replace("t", "").replace("hh", "h").replace("HH", "H").replace("'kl'.", ""); + timeFormat.replace(".ss", "").replace(":ss", "").replace("ss", ""); + return uiLang; +} + +QLocale getLocale() +{ + return loc; +} + +QString getDateFormat() +{ + return dateFormat; +} +void set_filename(const char *filename, bool force) +{ + if (!force && existing_filename) + return; + free((void *)existing_filename); + if (filename) + existing_filename = strdup(filename); + else + existing_filename = NULL; +} + +const QString get_dc_nickname(const char *model, uint32_t deviceid) +{ + const DiveComputerNode *existNode = dcList.getExact(model, deviceid); + + if (existNode && !existNode->nickName.isEmpty()) + return existNode->nickName; + else + return model; +} + +QString get_depth_string(int mm, bool showunit, bool showdecimal) +{ + if (prefs.units.length == units::METERS) { + double meters = mm / 1000.0; + return QString("%1%2").arg(meters, 0, 'f', (showdecimal && meters < 20.0) ? 1 : 0).arg(showunit ? translate("gettextFromC", "m") : ""); + } else { + double feet = mm_to_feet(mm); + return QString("%1%2").arg(feet, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "ft") : ""); + } +} + +QString get_depth_string(depth_t depth, bool showunit, bool showdecimal) +{ + return get_depth_string(depth.mm, showunit, showdecimal); +} + +QString get_depth_unit() +{ + if (prefs.units.length == units::METERS) + return QString("%1").arg(translate("gettextFromC", "m")); + else + return QString("%1").arg(translate("gettextFromC", "ft")); +} + +QString get_weight_string(weight_t weight, bool showunit) +{ + QString str = weight_string(weight.grams); + if (get_units()->weight == units::KG) { + str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "kg") : ""); + } else { + str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "lbs") : ""); + } + return (str); +} + +QString get_weight_unit() +{ + if (prefs.units.weight == units::KG) + return QString("%1").arg(translate("gettextFromC", "kg")); + else + return QString("%1").arg(translate("gettextFromC", "lbs")); +} + +/* these methods retrieve used gas per cylinder */ +static unsigned start_pressure(cylinder_t *cyl) +{ + return cyl->start.mbar ?: cyl->sample_start.mbar; +} + +static unsigned end_pressure(cylinder_t *cyl) +{ + return cyl->end.mbar ?: cyl->sample_end.mbar; +} + +QString get_cylinder_used_gas_string(cylinder_t *cyl, bool showunit) +{ + int decimals; + const char *unit; + double gas_usage; + /* Get the cylinder gas use in mbar */ + gas_usage = start_pressure(cyl) - end_pressure(cyl); + /* Can we turn it into a volume? */ + if (cyl->type.size.mliter) { + gas_usage = bar_to_atm(gas_usage / 1000); + gas_usage *= cyl->type.size.mliter; + gas_usage = get_volume_units(gas_usage, &decimals, &unit); + } else { + gas_usage = get_pressure_units(gas_usage, &unit); + decimals = 0; + } + // translate("gettextFromC","%.*f %s" + return QString("%1 %2").arg(gas_usage, 0, 'f', decimals).arg(showunit ? unit : ""); +} + +QString get_temperature_string(temperature_t temp, bool showunit) +{ + if (temp.mkelvin == 0) { + return ""; //temperature not defined + } else if (prefs.units.temperature == units::CELSIUS) { + double celsius = mkelvin_to_C(temp.mkelvin); + return QString("%1%2%3").arg(celsius, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "C") : ""); + } else { + double fahrenheit = mkelvin_to_F(temp.mkelvin); + return QString("%1%2%3").arg(fahrenheit, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "F") : ""); + } +} + +QString get_temp_unit() +{ + if (prefs.units.temperature == units::CELSIUS) + return QString(UTF8_DEGREE "C"); + else + return QString(UTF8_DEGREE "F"); +} + +QString get_volume_string(volume_t volume, bool showunit, int mbar) +{ + const char *unit; + int decimals; + double value = get_volume_units(volume.mliter, &decimals, &unit); + if (mbar) { + // we are showing a tank size + // fix the weird imperial way of denominating size and provide + // reasonable number of decimals + if (prefs.units.volume == units::CUFT) + value *= bar_to_atm(mbar / 1000.0); + decimals = (value > 20.0) ? 0 : (value > 2.0) ? 1 : 2; + } + return QString("%1%2").arg(value, 0, 'f', decimals).arg(showunit ? unit : ""); +} + +QString get_volume_unit() +{ + const char *unit; + (void) get_volume_units(0, NULL, &unit); + return QString(unit); +} + +QString get_pressure_string(pressure_t pressure, bool showunit) +{ + if (prefs.units.pressure == units::BAR) { + double bar = pressure.mbar / 1000.0; + return QString("%1%2").arg(bar, 0, 'f', 1).arg(showunit ? translate("gettextFromC", "bar") : ""); + } else { + double psi = mbar_to_PSI(pressure.mbar); + return QString("%1%2").arg(psi, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "psi") : ""); + } +} + +QString getSubsurfaceDataPath(QString folderToFind) +{ + QString execdir; + QDir folder; + + // first check if we are running in the build dir, so the path that we + // are looking for is just a subdirectory of the execution path; + // this also works on Windows as there we install the dirs + // under the application path + execdir = QCoreApplication::applicationDirPath(); + folder = QDir(execdir.append(QDir::separator()).append(folderToFind)); + if (folder.exists()) + return folder.absolutePath(); + + // next check for the Linux typical $(prefix)/share/subsurface + execdir = QCoreApplication::applicationDirPath(); + if (execdir.contains("bin")) { + folder = QDir(execdir.replace("bin", "share/subsurface/").append(folderToFind)); + if (folder.exists()) + return folder.absolutePath(); + } + // then look for the usual locations on a Mac + execdir = QCoreApplication::applicationDirPath(); + folder = QDir(execdir.append("/../Resources/share/").append(folderToFind)); + if (folder.exists()) + return folder.absolutePath(); + execdir = QCoreApplication::applicationDirPath(); + folder = QDir(execdir.append("/../Resources/").append(folderToFind)); + if (folder.exists()) + return folder.absolutePath(); + return QString(""); +} + +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 parseTemperatureToMkelvin(const QString &text) +{ + int mkelvin; + QString numOnly = text; + numOnly.replace(",", ".").remove(QRegExp("[^-0-9.]")); + if (numOnly.isEmpty()) + return 0; + double number = numOnly.toDouble(); + switch (prefs.units.temperature) { + case units::CELSIUS: + mkelvin = C_to_mkelvin(number); + break; + case units::FAHRENHEIT: + mkelvin = F_to_mkelvin(number); + break; + default: + mkelvin = 0; + } + return mkelvin; +} + +QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText) +{ + int hrs, mins; + mins = (when + 59) / 60; + hrs = mins / 60; + mins -= hrs * 60; + + QString displayTime; + if (hrs) + displayTime = QString("%1%2%3%4").arg(hrs).arg(hourText).arg(mins, 2, 10, QChar('0')).arg(minutesText); + else + displayTime = QString("%1%2").arg(mins).arg(minutesText); + + return displayTime; +} + +QString get_dive_date_string(timestamp_t when) +{ + QDateTime ts; + ts.setMSecsSinceEpoch(when * 1000L); + return loc.toString(ts.toUTC(), dateFormat + " " + timeFormat); +} + +QString get_short_dive_date_string(timestamp_t when) +{ + QDateTime ts; + ts.setMSecsSinceEpoch(when * 1000L); + return loc.toString(ts.toUTC(), shortDateFormat + " " + timeFormat); +} + +const char *get_dive_date_c_string(timestamp_t when) +{ + QString text = get_dive_date_string(when); + return strdup(text.toUtf8().data()); +} + +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(dateFormat) + 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 hashOf; +QMutex hashOfMutex; +QHash localFilenameOf; + +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; + hashfile.close(); + } +} + +void write_hashes() +{ + QSaveFile hashfile(hashfile_name()); + if (hashfile.open(QIODevice::WriteOnly)) { + QDataStream stream(&hashfile); + stream << localFilenameOf; + stream << hashOf; + 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)]; +} + +void updateHash(struct picture *picture) { + QByteArray hash = hashFile(fileFromHash(picture->hash)); + QMutexLocker locker(&hashOfMutex); + hashOf[QString(picture->filename)] = hash; + char *old = picture->hash; + picture->hash = strdup(hash.toHex()); + free(old); +} + +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); +} + +extern "C" void cache_picture(struct picture *picture) +{ + QString filename = picture->filename; + if (!hashOf.contains(filename)) + QtConcurrent::run(hashPicture, picture); +} + +void learnImages(const QDir dir, int max_recursions, bool recursed) +{ + QDir current(dir); + QStringList filters, files; + + if (max_recursions) { + foreach (QString dirname, dir.entryList(QStringList(), QDir::NoDotAndDotDot | QDir::Dirs)) { + learnImages(QDir(dir.filePath(dirname)), max_recursions - 1, true); + } + } + + foreach (QString format, QImageReader::supportedImageFormats()) { + filters.append(QString("*.").append(format)); + } + + foreach (QString file, dir.entryList(filters, QDir::Files)) { + files.append(dir.absoluteFilePath(file)); + } + + QtConcurrent::blockingMap(files, hashFile); +} + +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; + + 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("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(); + 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); + GET_BOOL("save_password_local", save_password_local); + 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); + + // 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); + + // 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(); + +} + +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"); +} diff --git a/subsurface-core/qthelper.h b/subsurface-core/qthelper.h new file mode 100644 index 000000000..a2b7b6c39 --- /dev/null +++ b/subsurface-core/qthelper.h @@ -0,0 +1,135 @@ +#ifndef QTHELPER_H +#define QTHELPER_H + +#include +#include +#include +#include "dive.h" +#include "divelist.h" +#include +#include + +class Dive { +private: + int m_number; + int m_id; + int m_rating; + QString m_date; + timestamp_t m_timestamp; + QString m_time; + QString m_location; + QString m_duration; + QString m_depth; + QString m_divemaster; + QString m_buddy; + QString m_airTemp; + QString m_waterTemp; + QString m_notes; + QString m_tags; + QString m_gas; + QString m_sac; + QString m_weight; + QString m_suit; + QString m_cylinder; + QString m_trip; + struct dive *dive; + void put_date_time(); + void put_timestamp(); + void put_location(); + void put_duration(); + void put_depth(); + void put_divemaster(); + void put_buddy(); + void put_temp(); + void put_notes(); + void put_tags(); + void put_gas(); + void put_sac(); + void put_weight(); + void put_suit(); + void put_cylinder(); + void put_trip(); + +public: + Dive(struct dive *dive) + : dive(dive) + { + m_number = dive->number; + m_id = dive->id; + m_rating = dive->rating; + put_date_time(); + put_location(); + put_duration(); + put_depth(); + put_divemaster(); + put_buddy(); + put_temp(); + put_notes(); + put_tags(); + put_gas(); + put_sac(); + put_timestamp(); + put_weight(); + put_suit(); + put_cylinder(); + put_trip(); + } + Dive(); + ~Dive(); + int number() const; + int id() const; + int rating() const; + QString date() const; + timestamp_t timestamp() const; + QString time() const; + QString location() const; + QString duration() 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 weight() const; + QString suit() const; + QString cylinder() const; + QString trip() const; +}; + +// 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 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, bool recursed); +void add_hash(const QString filename, QByteArray hash); +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); + +#endif // QTHELPER_H diff --git a/subsurface-core/qthelperfromc.h b/subsurface-core/qthelperfromc.h new file mode 100644 index 000000000..d2e80144c --- /dev/null +++ b/subsurface-core/qthelperfromc.h @@ -0,0 +1,21 @@ +#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(); + +#endif // QTHELPERFROMC_H diff --git a/subsurface-core/qtserialbluetooth.cpp b/subsurface-core/qtserialbluetooth.cpp new file mode 100644 index 000000000..025ab8c34 --- /dev/null +++ b/subsurface-core/qtserialbluetooth.cpp @@ -0,0 +1,415 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#if defined(SSRF_CUSTOM_SERIAL) + +#if defined(Q_OS_WIN) + #include + #include + #include +#endif + +#include + +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) +{ + 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 new file mode 100644 index 000000000..69ad0726d --- /dev/null +++ b/subsurface-core/save-git.c @@ -0,0 +1,1235 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dive.h" +#include "divelist.h" +#include "device.h" +#include "membuffer.h" +#include "git-access.h" +#include "version.h" +#include "qthelperfromc.h" + +/* + * handle libgit2 revision 0.20 and earlier + */ +#if !LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR <= 20 && !defined(USE_LIBGIT21_API) + #define GIT_CHECKOUT_OPTIONS_INIT GIT_CHECKOUT_OPTS_INIT + #define git_checkout_options git_checkout_opts + #define git_branch_create(out,repo,branch_name,target,force,sig,msg) \ + git_branch_create(out,repo,branch_name,target,force) + #define git_reference_set_target(out,ref,target,signature,log_message) \ + git_reference_set_target(out,ref,target) +#endif +/* + * api break in libgit2 revision 0.22 + */ +#if !LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR < 22 + #define git_treebuilder_new(out, repo, source) git_treebuilder_create(out, source) +#else + #define git_treebuilder_write(id, repo, bld) git_treebuilder_write(id, bld) +#endif +/* + * api break introduced in libgit2 master after 0.22 - let's guess this is the v0.23 API + */ +#if USE_LIBGIT23_API || (!LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR >= 23) + #define git_branch_create(out, repo, branch_name, target, force, signature, log_message) \ + git_branch_create(out, repo, branch_name, target, force) + #define git_reference_set_target(out, ref, id, author, log_message) \ + git_reference_set_target(out, ref, id, log_message) +#endif + +#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; +} + +/* + * 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 file at filepath to the git repo with given filename */ +static int blob_insert_fromdisk(git_repository *repo, struct dir *tree, const char *filepath, const char *filename) +{ + int ret; + git_oid blob_id; + + ret = git_blob_create_fromdisk(&blob_id, repo, filepath); + if (ret) + return ret; + return tree_insert(tree->files, filename, 1, &blob_id, GIT_FILEMODE_BLOB); +} + +/* + * 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 (!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); + } + 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) +{ + struct divecomputer *dc; + struct membuffer buf = { 0 }, name = { 0 }; + struct dir *subdir; + int ret, nr; + + /* Create dive directory */ + create_dive_name(dive, &name, tm); + 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) +{ + 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); + } + + 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) +{ + 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 */ + for_each_dive(i, dive) { + struct tm tm; + struct dir *tree; + + 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); + continue; + } + + save_one_dive(repo, tree, dive, &tm); + } + 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) +{ + 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_version()); +} + +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, author, "Create branch")) + 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, author, "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, repo, 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; + + if (verbose) + fprintf(stderr, "git storage: do git save\n"); + + /* 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)) + return -1; + + 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 new file mode 100644 index 000000000..64ce94f66 --- /dev/null +++ b/subsurface-core/save-html.c @@ -0,0 +1,533 @@ +#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_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 new file mode 100644 index 000000000..20743e90a --- /dev/null +++ b/subsurface-core/save-html.h @@ -0,0 +1,30 @@ +#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_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 new file mode 100644 index 000000000..166885861 --- /dev/null +++ b/subsurface-core/save-xml.c @@ -0,0 +1,743 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#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, " 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, " 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, " 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_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, " salinity, " salinity='", " g/l'"); + put_string(b, " />\n"); +} + +static void save_overview(struct membuffer *b, struct dive *dive) +{ + show_utf8(b, dive->divemaster, " ", "\n", 0); + show_utf8(b, dive->buddy, " ", "\n", 0); + show_utf8(b, dive->notes, " ", "\n", 0); + show_utf8(b, dive->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, " 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, " \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, " 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, " 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, " 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, " 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, " ", " min\n"); + save_extra_data(b, dc->extra_data); + save_events(b, dc->events); + save_samples(b, dc->samples, dc->sample); + + put_format(b, " \n"); +} + +static void save_picture(struct membuffer *b, struct picture *pic) +{ + put_string(b, " 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, "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, "\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, "when); + show_utf8(b, trip->location, " location=\'", "\'", 1); + put_format(b, ">\n"); + show_utf8(b, trip->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, "\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, "\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, "\n\n", DATAFORMAT_VERSION); + + if (prefs.save_userid_local) + put_format(b, " %30s\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, " \n"); + put_format(b, "\n"); + + /* save the dive sites - to make the output consistent let's sort the table, first */ + dive_site_table_sort(); + put_format(b, "\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, "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, " ", " \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, " category); + put_format(b, " origin='%d'", t->origin); + show_utf8(b, t->value, " value='", "'/>\n", 1); + } + } + } + put_format(b, "\n"); + } + put_format(b, "\n\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, "\n\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); + + try_to_backup(filename); + + save_dives_buffer(&buf, select_only); + + 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 new file mode 100644 index 000000000..cbac026cf --- /dev/null +++ b/subsurface-core/serial_ftdi.c @@ -0,0 +1,664 @@ +/* + * 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 // malloc, free +#include // strerror +#include // errno +#include // gettimeofday +#include // nanosleep +#include + +#include +#include + +#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 +#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 + +/* 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)) { + 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 new file mode 100644 index 000000000..acf8c5d9f --- /dev/null +++ b/subsurface-core/sha1.c @@ -0,0 +1,300 @@ +/* + * 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 +#include +#ifdef WIN32 +#include +#else +#include +#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 new file mode 100644 index 000000000..cab6ff77d --- /dev/null +++ b/subsurface-core/sha1.h @@ -0,0 +1,38 @@ +/* + * 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 new file mode 100644 index 000000000..19fd350eb --- /dev/null +++ b/subsurface-core/statistics.c @@ -0,0 +1,379 @@ +/* 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 +#include + +#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; + +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; + int 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; + + *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); + + size = sizeof(stats_t) * (dive_table.nr + 1); + stats_yearly = malloc(size); + stats_monthly = malloc(size); + stats_by_trip = malloc(size); + if (!stats_yearly || !stats_monthly || !stats_by_trip) + return; + memset(stats_yearly, 0, size); + memset(stats_monthly, 0, size); + memset(stats_by_trip, 0, size); + stats_yearly[0].is_year = 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; + + 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, int 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 == 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 */ + int 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 new file mode 100644 index 000000000..dbab25761 --- /dev/null +++ b/subsurface-core/statistics.h @@ -0,0 +1,58 @@ +/* + * 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 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, int 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 new file mode 100644 index 000000000..84e18b60f --- /dev/null +++ b/subsurface-core/strndup.h @@ -0,0 +1,21 @@ +#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 new file mode 100644 index 000000000..81e5d42d1 --- /dev/null +++ b/subsurface-core/strtod.c @@ -0,0 +1,128 @@ +/* + * 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 +#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/subsurfacestartup.c b/subsurface-core/subsurfacestartup.c new file mode 100644 index 000000000..1286885ee --- /dev/null +++ b/subsurface-core/subsurfacestartup.c @@ -0,0 +1,319 @@ +#include "subsurfacestartup.h" +#include "version.h" +#include +#include +#include "gettext.h" +#include "qthelperfromc.h" +#include "git-access.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 +}; + +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, "--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); +} + +void renumber_dives(int start_nr, bool selected_only) +{ + int i, nr = start_nr; + struct dive *dive; + + for_each_dive (i, dive) { + if (dive->selected) + dive->number = nr++; + } + mark_divelist_changed(true); +} + +/* + * 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->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) +{ + free((void*)prefs.default_filename); + free((void*)prefs.default_cylinder); + free((void*)prefs.divelist_font); + free((void*)prefs.cloud_storage_password); + free(prefs.proxy_host); + free(prefs.proxy_user); + free(prefs.proxy_pass); + free(prefs.userid); +} diff --git a/subsurface-core/subsurfacestartup.h b/subsurface-core/subsurfacestartup.h new file mode 100644 index 000000000..3ccc24aa4 --- /dev/null +++ b/subsurface-core/subsurfacestartup.h @@ -0,0 +1,26 @@ +#ifndef SUBSURFACESTARTUP_H +#define SUBSURFACESTARTUP_H + +#include "dive.h" +#include "divelist.h" +#include "libdivecomputer.h" + +#ifdef __cplusplus +extern "C" { +#else +#include +#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 new file mode 100644 index 000000000..a7173b169 --- /dev/null +++ b/subsurface-core/subsurfacesysinfo.cpp @@ -0,0 +1,620 @@ +/**************************************************************************** +** +** 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 + +#ifdef Q_OS_UNIX +#include +#endif + +#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) + +#ifndef QStringLiteral +# define QStringLiteral QString::fromUtf8 +#endif + +#ifdef Q_OS_UNIX +#include +#endif + +#ifdef __APPLE__ +#include +#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(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 new file mode 100644 index 000000000..b2c267b83 --- /dev/null +++ b/subsurface-core/subsurfacesysinfo.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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 + +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 new file mode 100644 index 000000000..670d85ad0 --- /dev/null +++ b/subsurface-core/taxonomy.c @@ -0,0 +1,48 @@ +#include "taxonomy.h" +#include "gettext.h" +#include + +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 new file mode 100644 index 000000000..51245d562 --- /dev/null +++ b/subsurface-core/taxonomy.h @@ -0,0 +1,41 @@ +#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 new file mode 100644 index 000000000..b658954bc --- /dev/null +++ b/subsurface-core/time.c @@ -0,0 +1,98 @@ +#include +#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 int mdays[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + }; + static const int mdays_leap[] = { + 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + }; + unsigned long val; + unsigned int leapyears; + int m; + const 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 new file mode 100644 index 000000000..2a6e5178c --- /dev/null +++ b/subsurface-core/uemis-downloader.c @@ -0,0 +1,1359 @@ +/* + * uemis-downloader.c + * + * Copyright (c) Dirk Hohndel + * released under GPL2 + * + * very (VERY) loosely based on the algorithms found in Java code by Fabian Gast + * 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 +#include +#include +#include +#include +#include + +#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; + +/* 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; +} + +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 protocoll 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 delimter ('{') */ +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 + if (write(reqtxt_file, sb, strlen(sb)) != 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); + 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 adresses 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, bool keep_number, 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]; + int 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 dont 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 varible 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-
*/ + 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; + } + } + 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 memeory for a full round + * UEMIS_MEM_CRITICAL if the memory is good for reading the dive logs + * UEMIS_MEM_FULL if the memory is exhaused + */ +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 (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 duplicat and can be deleted */ + if (nds->uuid != ods->uuid) { + delete_dive_site(nds->uuid); + dive->dive_site_uuid = ods->uuid; + } + } + } else { + /* if we cant 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; + int deleted_files = 0; + + snprintf(log_file_no_to_find, sizeof(log_file_no_to_find), "logfilenr{int{%d", dive->dc.diveid); + 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 sucessfull even if the dive has been deleted. */ + found = true; + if (strstr(mbuf, "deleted{bool{true") == NULL) { + process_raw_buffer(data, deviceidnr, mbuf, &newmax, false, 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++; + /* 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\n", d_time, dive->dc.diveid); +#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) + dive_to_read = dive_to_read - 2; + if (dive_to_read < -1) + dive_to_read = -1; + } + } + } + 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, keep_number = false, 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 + if (dive_table.nr == 0) + keep_number = true; + + 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); + + 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); + if (success && mbuf && uemis_mem_status != UEMIS_MEM_FULL) { +#if UEMIS_DEBUG & 16 + do_dump_buffer_to_file(mbuf, "Divelogs"); +#endif + /* process the buffer we have assembled */ + + if (!process_raw_buffer(data, deviceidnr, mbuf, &newmax, keep_number, 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(mbuf); + if (t && atoi(t) > start) + start = atoi(t); + free(t); + once = false; + } + /* clean up mbuf */ + endptr = strstr(mbuf, "{{{"); + 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 deatils 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) + break; + + /* if the user clicked cancel, exit gracefully */ + if (import_thread_cancelled) + break; + + /* if we got an error or got nothing back, stop trying */ + if (!success || !param_buff[3]) + break; +#if UEMIS_DEBUG & 2 + if (debug_round != -1) + if (debug_round-- == 0) + goto bail; +#endif + } else { + /* some of the loading from the UEMIS failed at the divelog level + * if the memory status = full, we cant even load the divespots and/or buddys. + * 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); + 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 new file mode 100644 index 000000000..4135e0cfe --- /dev/null +++ b/subsurface-core/uemis.c @@ -0,0 +1,392 @@ +/* + * uemis.c + * + * UEMIS SDA file importer + * AUTHOR: Dirk Hohndel - Copyright 2011 + * + * Licensed under the MIT license. + */ +#include +#include + +#include "gettext.h" + +#include "dive.h" +#include "uemis.h" +#include +#include + +/* + * 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 { + int 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(int 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(int 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 new file mode 100644 index 000000000..5f32fe76c --- /dev/null +++ b/subsurface-core/uemis.h @@ -0,0 +1,54 @@ +/* + * defines and prototypes for the uemis Zurich SDA file parser + */ + +#ifndef UEMIS_H +#define UEMIS_H + +#include +#include "dive.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void uemis_parse_divelog_binary(char *base64, void *divep); +int uemis_get_weight_unit(int 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 new file mode 100644 index 000000000..1273bd9bb --- /dev/null +++ b/subsurface-core/units.h @@ -0,0 +1,277 @@ +#ifndef UNITS_H +#define UNITS_H + +#include + +#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 +/* + * 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 +{ + int32_t mkelvin; // up to 1750 degrees K +} 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 { + METERS, + FEET + } length; + enum { + LITER, + CUFT + } volume; + enum { + BAR, + PSI, + PASCAL + } pressure; + enum { + CELSIUS, + FAHRENHEIT, + KELVIN + } temperature; + enum { + KG, + LBS + } weight; + enum { + 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 new file mode 100644 index 000000000..5b54bf4c7 --- /dev/null +++ b/subsurface-core/version.c @@ -0,0 +1,16 @@ +#include "ssrf-version.h" + +const char *subsurface_version(void) +{ + return VERSION_STRING; +} + +const char *subsurface_git_version(void) +{ + return GIT_VERSION_STRING; +} + +const char *subsurface_canonical_version(void) +{ + return CANONICAL_VERSION_STRING; +} diff --git a/subsurface-core/version.h b/subsurface-core/version.h new file mode 100644 index 000000000..bc0aac00d --- /dev/null +++ b/subsurface-core/version.h @@ -0,0 +1,16 @@ +#ifndef VERSION_H +#define VERSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +const char *subsurface_version(void); +const char *subsurface_git_version(void); +const char *subsurface_canonical_version(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsurface-core/webservice.h b/subsurface-core/webservice.h new file mode 100644 index 000000000..052b8aae7 --- /dev/null +++ b/subsurface-core/webservice.h @@ -0,0 +1,24 @@ +#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 new file mode 100644 index 000000000..a2386fd83 --- /dev/null +++ b/subsurface-core/windows.c @@ -0,0 +1,448 @@ +/* windows.c */ +/* implements Windows specific functions */ +#include +#include "dive.h" +#include "display.h" +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x500 +#include +#include +#include +#include +#include +#include +#include +#include + +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 "\\.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 +} diff --git a/subsurface-core/windowtitleupdate.cpp b/subsurface-core/windowtitleupdate.cpp new file mode 100644 index 000000000..eec324c68 --- /dev/null +++ b/subsurface-core/windowtitleupdate.cpp @@ -0,0 +1,31 @@ +#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(); + wt->emitSignal(); +} diff --git a/subsurface-core/windowtitleupdate.h b/subsurface-core/windowtitleupdate.h new file mode 100644 index 000000000..8650e5868 --- /dev/null +++ b/subsurface-core/windowtitleupdate.h @@ -0,0 +1,20 @@ +#ifndef WINDOWTITLEUPDATE_H +#define WINDOWTITLEUPDATE_H + +#include + +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 new file mode 100644 index 000000000..177443563 --- /dev/null +++ b/subsurface-core/worldmap-options.h @@ -0,0 +1,7 @@ +#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 new file mode 100644 index 000000000..25c5ee33f --- /dev/null +++ b/subsurface-core/worldmap-save.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include + +#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: '
'+'
'+'
'+'
"); + snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Date:")); + put_HTML_date(b, dive, pre, "

"); + snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Time:")); + put_HTML_time(b, dive, pre, "

"); + snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Duration:")); + snprintf(post, sizeof(post), " %s

", translate("gettextFromC", "min")); + put_duration(b, dive->duration, pre, post); + snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Max. depth:")); + snprintf(post, sizeof(post), " %s

", translate("gettextFromC", "m")); + put_depth(b, dive->maxdepth, pre, post); + put_string(b, "

"); + put_HTML_quoted(b, translate("gettextFromC", "Air temp.:")); + put_HTML_airtemp(b, dive, " ", "

"); + put_string(b, "

"); + put_HTML_quoted(b, translate("gettextFromC", "Water temp.:")); + put_HTML_watertemp(b, dive, " ", "

"); + snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Location:")); + put_string(b, pre); + put_HTML_quoted(b, get_dive_location(dive)); + put_string(b, "

"); + snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Notes:")); + put_HTML_notes(b, dive, pre, "

"); + put_string(b, "

'+'
'+'
'});\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, "\n\n\n"); + put_string(b, "\nWorld Map\n"); + put_string(b, ""); +} + +void insert_css(struct membuffer *b) +{ + put_format(b, "\n", css); +} + +void insert_javascript(struct membuffer *b, const bool selected_only) +{ + put_string(b, "\n\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\n\n
\n\n"); +} + +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 new file mode 100644 index 000000000..102ea40e5 --- /dev/null +++ b/subsurface-core/worldmap-save.h @@ -0,0 +1,15 @@ +#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 diff --git a/subsurfacestartup.c b/subsurfacestartup.c deleted file mode 100644 index 1286885ee..000000000 --- a/subsurfacestartup.c +++ /dev/null @@ -1,319 +0,0 @@ -#include "subsurfacestartup.h" -#include "version.h" -#include -#include -#include "gettext.h" -#include "qthelperfromc.h" -#include "git-access.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 -}; - -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, "--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); -} - -void renumber_dives(int start_nr, bool selected_only) -{ - int i, nr = start_nr; - struct dive *dive; - - for_each_dive (i, dive) { - if (dive->selected) - dive->number = nr++; - } - mark_divelist_changed(true); -} - -/* - * 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->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) -{ - free((void*)prefs.default_filename); - free((void*)prefs.default_cylinder); - free((void*)prefs.divelist_font); - free((void*)prefs.cloud_storage_password); - free(prefs.proxy_host); - free(prefs.proxy_user); - free(prefs.proxy_pass); - free(prefs.userid); -} diff --git a/subsurfacestartup.h b/subsurfacestartup.h deleted file mode 100644 index 3ccc24aa4..000000000 --- a/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 -#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/subsurfacesysinfo.cpp b/subsurfacesysinfo.cpp deleted file mode 100644 index a7173b169..000000000 --- a/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 - -#ifdef Q_OS_UNIX -#include -#endif - -#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) - -#ifndef QStringLiteral -# define QStringLiteral QString::fromUtf8 -#endif - -#ifdef Q_OS_UNIX -#include -#endif - -#ifdef __APPLE__ -#include -#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(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/subsurfacesysinfo.h b/subsurfacesysinfo.h deleted file mode 100644 index b2c267b83..000000000 --- a/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 - -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/taxonomy.c b/taxonomy.c deleted file mode 100644 index 670d85ad0..000000000 --- a/taxonomy.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "taxonomy.h" -#include "gettext.h" -#include - -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/taxonomy.h b/taxonomy.h deleted file mode 100644 index 51245d562..000000000 --- a/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/time.c b/time.c deleted file mode 100644 index b658954bc..000000000 --- a/time.c +++ /dev/null @@ -1,98 +0,0 @@ -#include -#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 int mdays[] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, - }; - static const int mdays_leap[] = { - 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, - }; - unsigned long val; - unsigned int leapyears; - int m; - const 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/uemis-downloader.c b/uemis-downloader.c deleted file mode 100644 index 2a6e5178c..000000000 --- a/uemis-downloader.c +++ /dev/null @@ -1,1359 +0,0 @@ -/* - * uemis-downloader.c - * - * Copyright (c) Dirk Hohndel - * released under GPL2 - * - * very (VERY) loosely based on the algorithms found in Java code by Fabian Gast - * 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 -#include -#include -#include -#include -#include - -#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; - -/* 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; -} - -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 protocoll 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 delimter ('{') */ -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 - if (write(reqtxt_file, sb, strlen(sb)) != 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); - 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 adresses 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, bool keep_number, 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]; - int 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 dont 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 varible 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-
*/ - 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; - } - } - 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 memeory for a full round - * UEMIS_MEM_CRITICAL if the memory is good for reading the dive logs - * UEMIS_MEM_FULL if the memory is exhaused - */ -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 (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 duplicat and can be deleted */ - if (nds->uuid != ods->uuid) { - delete_dive_site(nds->uuid); - dive->dive_site_uuid = ods->uuid; - } - } - } else { - /* if we cant 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; - int deleted_files = 0; - - snprintf(log_file_no_to_find, sizeof(log_file_no_to_find), "logfilenr{int{%d", dive->dc.diveid); - 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 sucessfull even if the dive has been deleted. */ - found = true; - if (strstr(mbuf, "deleted{bool{true") == NULL) { - process_raw_buffer(data, deviceidnr, mbuf, &newmax, false, 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++; - /* 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\n", d_time, dive->dc.diveid); -#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) - dive_to_read = dive_to_read - 2; - if (dive_to_read < -1) - dive_to_read = -1; - } - } - } - 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, keep_number = false, 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 - if (dive_table.nr == 0) - keep_number = true; - - 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); - - 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); - if (success && mbuf && uemis_mem_status != UEMIS_MEM_FULL) { -#if UEMIS_DEBUG & 16 - do_dump_buffer_to_file(mbuf, "Divelogs"); -#endif - /* process the buffer we have assembled */ - - if (!process_raw_buffer(data, deviceidnr, mbuf, &newmax, keep_number, 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(mbuf); - if (t && atoi(t) > start) - start = atoi(t); - free(t); - once = false; - } - /* clean up mbuf */ - endptr = strstr(mbuf, "{{{"); - 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 deatils 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) - break; - - /* if the user clicked cancel, exit gracefully */ - if (import_thread_cancelled) - break; - - /* if we got an error or got nothing back, stop trying */ - if (!success || !param_buff[3]) - break; -#if UEMIS_DEBUG & 2 - if (debug_round != -1) - if (debug_round-- == 0) - goto bail; -#endif - } else { - /* some of the loading from the UEMIS failed at the divelog level - * if the memory status = full, we cant even load the divespots and/or buddys. - * 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); - 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/uemis.c b/uemis.c deleted file mode 100644 index 4135e0cfe..000000000 --- a/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 -#include - -#include "gettext.h" - -#include "dive.h" -#include "uemis.h" -#include -#include - -/* - * 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 { - int 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(int 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(int 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/uemis.h b/uemis.h deleted file mode 100644 index 5f32fe76c..000000000 --- a/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 -#include "dive.h" - -#ifdef __cplusplus -extern "C" { -#endif - -void uemis_parse_divelog_binary(char *base64, void *divep); -int uemis_get_weight_unit(int 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/units.h b/units.h deleted file mode 100644 index 1273bd9bb..000000000 --- a/units.h +++ /dev/null @@ -1,277 +0,0 @@ -#ifndef UNITS_H -#define UNITS_H - -#include - -#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 -/* - * 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 -{ - int32_t mkelvin; // up to 1750 degrees K -} 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 { - METERS, - FEET - } length; - enum { - LITER, - CUFT - } volume; - enum { - BAR, - PSI, - PASCAL - } pressure; - enum { - CELSIUS, - FAHRENHEIT, - KELVIN - } temperature; - enum { - KG, - LBS - } weight; - enum { - 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/version.c b/version.c deleted file mode 100644 index 5b54bf4c7..000000000 --- a/version.c +++ /dev/null @@ -1,16 +0,0 @@ -#include "ssrf-version.h" - -const char *subsurface_version(void) -{ - return VERSION_STRING; -} - -const char *subsurface_git_version(void) -{ - return GIT_VERSION_STRING; -} - -const char *subsurface_canonical_version(void) -{ - return CANONICAL_VERSION_STRING; -} diff --git a/version.h b/version.h deleted file mode 100644 index bc0aac00d..000000000 --- a/version.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef VERSION_H -#define VERSION_H - -#ifdef __cplusplus -extern "C" { -#endif - -const char *subsurface_version(void); -const char *subsurface_git_version(void); -const char *subsurface_canonical_version(void); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/webservice.h b/webservice.h deleted file mode 100644 index 052b8aae7..000000000 --- a/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/windows.c b/windows.c deleted file mode 100644 index a2386fd83..000000000 --- a/windows.c +++ /dev/null @@ -1,448 +0,0 @@ -/* windows.c */ -/* implements Windows specific functions */ -#include -#include "dive.h" -#include "display.h" -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x500 -#include -#include -#include -#include -#include -#include -#include -#include - -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 "\\.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 -} diff --git a/windowtitleupdate.cpp b/windowtitleupdate.cpp deleted file mode 100644 index eec324c68..000000000 --- a/windowtitleupdate.cpp +++ /dev/null @@ -1,31 +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(); - wt->emitSignal(); -} diff --git a/windowtitleupdate.h b/windowtitleupdate.h deleted file mode 100644 index 8650e5868..000000000 --- a/windowtitleupdate.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef WINDOWTITLEUPDATE_H -#define WINDOWTITLEUPDATE_H - -#include - -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/worldmap-options.h b/worldmap-options.h deleted file mode 100644 index 177443563..000000000 --- a/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/worldmap-save.c b/worldmap-save.c deleted file mode 100644 index 25c5ee33f..000000000 --- a/worldmap-save.c +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include -#include -#include - -#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: '
'+'
'+'
'+'
"); - snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Date:")); - put_HTML_date(b, dive, pre, "

"); - snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Time:")); - put_HTML_time(b, dive, pre, "

"); - snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Duration:")); - snprintf(post, sizeof(post), " %s

", translate("gettextFromC", "min")); - put_duration(b, dive->duration, pre, post); - snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Max. depth:")); - snprintf(post, sizeof(post), " %s

", translate("gettextFromC", "m")); - put_depth(b, dive->maxdepth, pre, post); - put_string(b, "

"); - put_HTML_quoted(b, translate("gettextFromC", "Air temp.:")); - put_HTML_airtemp(b, dive, " ", "

"); - put_string(b, "

"); - put_HTML_quoted(b, translate("gettextFromC", "Water temp.:")); - put_HTML_watertemp(b, dive, " ", "

"); - snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Location:")); - put_string(b, pre); - put_HTML_quoted(b, get_dive_location(dive)); - put_string(b, "

"); - snprintf(pre, sizeof(pre), "

%s ", translate("gettextFromC", "Notes:")); - put_HTML_notes(b, dive, pre, "

"); - put_string(b, "

'+'
'+'
'});\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, "\n\n\n"); - put_string(b, "\nWorld Map\n"); - put_string(b, ""); -} - -void insert_css(struct membuffer *b) -{ - put_format(b, "\n", css); -} - -void insert_javascript(struct membuffer *b, const bool selected_only) -{ - put_string(b, "\n\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\n\n
\n\n"); -} - -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/worldmap-save.h b/worldmap-save.h deleted file mode 100644 index 102ea40e5..000000000 --- a/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 -- cgit v1.2.3-70-g09d2